file.es6.js 9.6 KB

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