Drupal - Calling a JS function after managed file has completed uploading

This is tricky in D8, as the ajax code does not offer any hooks or callbacks after the function has run. The solution is to override the ajax callback function that the managed_file element uses, adding an additional custom command that is triggered when the file upload is complete.

First, we create a custom AJAX command, that will be used to trigger the code we want to execute. Details on creating custom ajax commands can be found here.

First, the Ajax command on the PHP side. This will go into [MODULE]/src/Ajax:

<?php

namespace Drupal\[MODULE]\Ajax;

use Drupal\Core\Ajax\CommandInterface;

/**
 * Command to trigger an event when managed file upload is complete.
 */
class ManagedFileUploadCompleteEventCommand implements CommandInterface {

  /**
   * Implements Drupal\Core\Ajax\CommandInterface:render().
   */
  public function render() {
    return [
      'command' => 'triggerManagedFileUploadComplete',
    ];
  }

}

Next, the triggerManagedFileUploadComplete() command needs to be created on the JavaScript side. This is done in [MODULE]/js/managed_file_upload_complete_event_command.js.

(function (Drupal) {

  "use strict";

  /**
   * Add new custom command.
   */
  Drupal.AjaxCommands.prototype.triggerManagedFileUploadComplete = function () {
    // Do stuff here after file upload is complete.
    alert(Drupal.t("File upload complete!"));
  };

}(Drupal));

Now the new JavaScript file needs to be registered in MODULE.libraries.yml:

command.managed_file_upload_complete_event_command:
  js:
    js/managed_file_upload_complete_event_command.js: {}
  dependencies:
    - core/jquery
    - core/jquery.once

With this, the custom command can be attached using the library: [MODULE]/command.managed_file_upload_complete_event_command

Now that the custom command and it's related JS callback have been completed, the next thing is to add the new command onto the end of the ajax callback, so it is executed when the upload is complete.

The default ajax callback for managed_file elements is \Drupal\file\Element\ManagedFile::uploadAjaxCallback(). This needs to be overridden, adding the command created above to be run after all other ajax commands.

The #ajax property of the managed_file element is added in \Drupal\file\Element\ManagedFile::processManagedFile(). As such, if we want to alter the ajax, it has to happen after the above #process handler has been run. So we add another #process callback to be called after the default #process callback. In our process callback, we can change the #ajax callback to a function of our own. The #process callbacks are declared in \Drupal\file\Element\ManagedFile::getInfo(). Adding another #process handler can therefore be done in hook_element_info_alter().

/**
 * Implements hook_element_info_alter().
 */
function MODULE_element_info_alter(array &$info) {
  // Add a custom #process hook to the managed_file element:
  $info['managed_file']['#process'][] = 'MODULE_managed_file_process';
  // Add the custom command to managed_file elements, so that it is
  // available when called:
  $info['managed_file']['#attached']['library'][] = '[MODULE]/command.managed_file_upload_complete_event_command';
}

Now the command has been created and attached to all managed_file elements. The next step is to override the #ajax callback with our own, adding the custom command to it. Let's look at the overridden function (should be put in the .module file):

/**
 * Custom ajax callback for managed files.
 *
 * Overrides \Drupal\file\Element\ManagedFile::uploadAjaxCallback()
 *
 * @see \Drupal\file\Element\ManagedFile::uploadAjaxCallback
 */
function MODULE_managed_file_ajax_callback(array &$form, FormStateInterface $form_state) {
  // Retrieve the original response.
  $response = \Drupal\file\Element\ManagedFile::uploadAjaxCallback($form, $form_state, \Drupal::request());

  // Add our own command to the end, so our command is run last:
  $response->addCommand(new \Drupal\[MODULE]\Ajax\ManagedFileUploadCompleteEventCommand());

  return $response;
}

The last thing to do is to replace the original ajax command with our own. This is done in the process callback we registered in hook_widget_info_alter():

/**
 * Custom process callback added to managed_file elements.
 *
 * Replaces the original #ajax callback with a custom one.
 */
function MODULE_managed_file_process(array &$element, FormStateInterface $form_state) {
  $element['upload_button']['#ajax']['callback'] = 'MODULE_managed_file_ajax_callback';

  return $element;
}

This tells Drupal to replace the original ajax callback with MODULE_managed_file_ajax_callback(). This callback retrieves the response from the original callback, and adds our custom command to the end of it. The custom command is called when the file has been uploaded, allowing us to run whatever JS we need on the server side after files have completed uploading.

Tags:

Forms

Ajax

8