file.es6.js 9.0 KB

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