1/** 2 * @file 3 * Provides JavaScript additions to the managed file field type. 4 * 5 * This file provides progress bar support (if available), popup windows for 6 * file previews, and disabling of other file fields during Ajax uploads (which 7 * prevents separate file fields from accidentally uploading files). 8 */ 9 10(function ($, Drupal) { 11 /** 12 * Attach behaviors to the file fields passed in the settings. 13 * 14 * @type {Drupal~behavior} 15 * 16 * @prop {Drupal~behaviorAttach} attach 17 * Attaches validation for file extensions. 18 * @prop {Drupal~behaviorDetach} detach 19 * Detaches validation for file extensions. 20 */ 21 Drupal.behaviors.fileValidateAutoAttach = { 22 attach(context, settings) { 23 const $context = $(context); 24 let elements; 25 26 function initFileValidation(selector) { 27 $(once('fileValidate', $context.find(selector))).on( 28 'change.fileValidate', 29 { extensions: elements[selector] }, 30 Drupal.file.validateExtension, 31 ); 32 } 33 34 if (settings.file && settings.file.elements) { 35 elements = settings.file.elements; 36 Object.keys(elements).forEach(initFileValidation); 37 } 38 }, 39 detach(context, settings, trigger) { 40 const $context = $(context); 41 let elements; 42 43 function removeFileValidation(selector) { 44 $(once.remove('fileValidate', $context.find(selector))).off( 45 'change.fileValidate', 46 Drupal.file.validateExtension, 47 ); 48 } 49 50 if (trigger === 'unload' && settings.file && settings.file.elements) { 51 elements = settings.file.elements; 52 Object.keys(elements).forEach(removeFileValidation); 53 } 54 }, 55 }; 56 57 /** 58 * Attach behaviors to file element auto upload. 59 * 60 * @type {Drupal~behavior} 61 * 62 * @prop {Drupal~behaviorAttach} attach 63 * Attaches triggers for the upload button. 64 * @prop {Drupal~behaviorDetach} detach 65 * Detaches auto file upload trigger. 66 */ 67 Drupal.behaviors.fileAutoUpload = { 68 attach(context) { 69 $(once('auto-file-upload', 'input[type="file"]', context)).on( 70 'change.autoFileUpload', 71 Drupal.file.triggerUploadButton, 72 ); 73 }, 74 detach(context, settings, trigger) { 75 if (trigger === 'unload') { 76 $(once.remove('auto-file-upload', 'input[type="file"]', context)).off( 77 '.autoFileUpload', 78 ); 79 } 80 }, 81 }; 82 83 /** 84 * Attach behaviors to the file upload and remove buttons. 85 * 86 * @type {Drupal~behavior} 87 * 88 * @prop {Drupal~behaviorAttach} attach 89 * Attaches form submit events. 90 * @prop {Drupal~behaviorDetach} detach 91 * Detaches form submit events. 92 */ 93 Drupal.behaviors.fileButtons = { 94 attach(context) { 95 const $context = $(context); 96 $context 97 .find('.js-form-submit') 98 .on('mousedown', Drupal.file.disableFields); 99 $context 100 .find('.js-form-managed-file .js-form-submit') 101 .on('mousedown', Drupal.file.progressBar); 102 }, 103 detach(context, settings, trigger) { 104 if (trigger === 'unload') { 105 const $context = $(context); 106 $context 107 .find('.js-form-submit') 108 .off('mousedown', Drupal.file.disableFields); 109 $context 110 .find('.js-form-managed-file .js-form-submit') 111 .off('mousedown', Drupal.file.progressBar); 112 } 113 }, 114 }; 115 116 /** 117 * Attach behaviors to links within managed file elements for preview windows. 118 * 119 * @type {Drupal~behavior} 120 * 121 * @prop {Drupal~behaviorAttach} attach 122 * Attaches triggers. 123 * @prop {Drupal~behaviorDetach} detach 124 * Detaches triggers. 125 */ 126 Drupal.behaviors.filePreviewLinks = { 127 attach(context) { 128 $(context) 129 .find('div.js-form-managed-file .file a') 130 .on('click', Drupal.file.openInNewWindow); 131 }, 132 detach(context) { 133 $(context) 134 .find('div.js-form-managed-file .file a') 135 .off('click', Drupal.file.openInNewWindow); 136 }, 137 }; 138 139 /** 140 * File upload utility functions. 141 * 142 * @namespace 143 */ 144 Drupal.file = Drupal.file || { 145 /** 146 * Client-side file input validation of file extensions. 147 * 148 * @name Drupal.file.validateExtension 149 * 150 * @param {jQuery.Event} event 151 * The event triggered. For example `change.fileValidate`. 152 */ 153 validateExtension(event) { 154 event.preventDefault(); 155 // Remove any previous errors. 156 $('.file-upload-js-error').remove(); 157 158 // Add client side validation for the input[type=file]. 159 const extensionPattern = event.data.extensions.replace(/,\s*/g, '|'); 160 if (extensionPattern.length > 1 && this.value.length > 0) { 161 const acceptableMatch = new RegExp(`\\.(${extensionPattern})$`, 'gi'); 162 if (!acceptableMatch.test(this.value)) { 163 const error = Drupal.t( 164 'The selected file %filename cannot be uploaded. Only files with the following extensions are allowed: %extensions.', 165 { 166 // According to the specifications of HTML5, a file upload control 167 // should not reveal the real local path to the file that a user 168 // has selected. Some web browsers implement this restriction by 169 // replacing the local path with "C:\fakepath\", which can cause 170 // confusion by leaving the user thinking perhaps Drupal could not 171 // find the file because it messed up the file path. To avoid this 172 // confusion, therefore, we strip out the bogus fakepath string. 173 '%filename': this.value.replace('C:\\fakepath\\', ''), 174 '%extensions': extensionPattern.replace(/\|/g, ', '), 175 }, 176 ); 177 $(this) 178 .closest('div.js-form-managed-file') 179 .prepend( 180 `<div class="messages messages--error file-upload-js-error" aria-live="polite">${error}</div>`, 181 ); 182 this.value = ''; 183 // Cancel all other change event handlers. 184 event.stopImmediatePropagation(); 185 } 186 } 187 }, 188 189 /** 190 * Trigger the upload_button mouse event to auto-upload as a managed file. 191 * 192 * @name Drupal.file.triggerUploadButton 193 * 194 * @param {jQuery.Event} event 195 * The event triggered. For example `change.autoFileUpload`. 196 */ 197 triggerUploadButton(event) { 198 $(event.target) 199 .closest('.js-form-managed-file') 200 .find('.js-form-submit[data-drupal-selector$="upload-button"]') 201 .trigger('mousedown'); 202 }, 203 204 /** 205 * Prevent file uploads when using buttons not intended to upload. 206 * 207 * @name Drupal.file.disableFields 208 * 209 * @param {jQuery.Event} event 210 * The event triggered, most likely a `mousedown` event. 211 */ 212 disableFields(event) { 213 const $clickedButton = $(this); 214 $clickedButton.trigger('formUpdated'); 215 216 // Check if we're working with an "Upload" button. 217 let $enabledFields = []; 218 if ($clickedButton.closest('div.js-form-managed-file').length > 0) { 219 $enabledFields = $clickedButton 220 .closest('div.js-form-managed-file') 221 .find('input.js-form-file'); 222 } 223 224 // Temporarily disable upload fields other than the one we're currently 225 // working with. Filter out fields that are already disabled so that they 226 // do not get enabled when we re-enable these fields at the end of 227 // behavior processing. Re-enable in a setTimeout set to a relatively
228 // short amount of time (1 second). All the other mousedown handlers 229 // (like Drupal's Ajax behaviors) are executed before any timeout 230 // functions are called, so we don't have to worry about the fields being 231 // re-enabled too soon. @todo If the previous sentence is true, why not 232 // set the timeout to 0? 233 const $fieldsToTemporarilyDisable = $( 234 'div.js-form-managed-file input.js-form-file', 235 ) 236 .not($enabledFields) 237 .not(':disabled'); 238 $fieldsToTemporarilyDisable.prop('disabled', true); 239 setTimeout(() => { 240 $fieldsToTemporarilyDisable.prop('disabled', false); 241 }, 1000); 242 }, 243 244 /** 245 * Add progress bar support if possible. 246 * 247 * @name Drupal.file.progressBar 248 * 249 * @param {jQuery.Event} event 250 * The event triggered, most likely a `mousedown` event. 251 */ 252 progressBar(event) { 253 const $clickedButton = $(this); 254 const $progressId = $clickedButton 255 .closest('div.js-form-managed-file') 256 .find('input.file-progress'); 257 if ($progressId.length) { 258 const originalName = $progressId.attr('name'); 259 260 // Replace the name with the required identifier. 261 $progressId.attr( 262 'name', 263 originalName.match(/APC_UPLOAD_PROGRESS|UPLOAD_IDENTIFIER/)[0], 264 ); 265 266 // Restore the original name after the upload begins. 267 setTimeout(() => { 268 $progressId.attr('name', originalName); 269 }, 1000); 270 } 271 // Show the progress bar if the upload takes longer than half a second. 272 setTimeout(() => { 273 $clickedButton 274 .closest('div.js-form-managed-file') 275 .find('div.ajax-progress-bar') 276 .slideDown(); 277 }, 500); 278 $clickedButton.trigger('fileUpload'); 279 }, 280 281 /** 282 * Open links to files within forms in a new window. 283 * 284 * @name Drupal.file.openInNewWindow 285 * 286 * @param {jQuery.Event} event 287 * The event triggered, most likely a `click` event. 288 */ 289 openInNewWindow(event) { 290 event.preventDefault(); 291 $(this).attr('target', '_blank'); 292 window.open( 293 this.href, 294 'filePreview', 295 'toolbar=0,scrollbars=1,location=1,statusbar=1,menubar=0,resizable=1,width=500,height=550', 296 ); 297 }, 298 }; 299})(jQuery, Drupal);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.