Comment puis-je appeler une fonction JavaScript une fois le téléchargement d'un fichier terminé à l'aide de l'API de formulaire Drupal Form (FAPI) managed_file
élément?
C'est délicat en D8, car le code ajax n'offre aucun hook ou rappel après l'exécution de la fonction. La solution consiste à remplacer la fonction de rappel ajax utilisée par l'élément managed_file, en ajoutant une commande personnalisée supplémentaire qui est déclenchée lorsque le téléchargement du fichier est terminé.
Tout d'abord, nous créons une commande AJAX, qui sera utilisée pour déclencher le code que nous voulons exécuter. Des détails sur la création de commandes ajax personnalisées peuvent être trouvés ici .
Tout d'abord, la commande Ajax du côté PHP. Cela ira dans [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',
];
}
}
Ensuite, la commande triggerManagedFileUploadComplete()
doit être créée côté JavaScript. Cela se fait dans [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));
Maintenant, le nouveau fichier JavaScript doit être enregistré dans 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
Avec cela, la commande personnalisée peut être attachée en utilisant la bibliothèque: [MODULE]/command.managed_file_upload_complete_event_command
Maintenant que la commande personnalisée et son rappel JS associé sont terminés, la prochaine chose consiste à ajouter la nouvelle commande à la fin du rappel ajax, afin qu'elle soit exécutée lorsque le téléchargement est terminé.
Le rappel ajax par défaut pour les éléments managed_file
Est \ Drupal\file\Element\ManagedFile :: uploadAjaxCallback () . Cela doit être remplacé, en ajoutant la commande créée ci-dessus pour être exécutée après toutes les autres commandes ajax.
La propriété #ajax de l'élément managed_file
Est ajoutée dans \ Drupal\file\Element\ManagedFile :: processManagedFile () . En tant que tel, si nous voulons modifier l'ajax, cela doit se produire après l'exécution du gestionnaire #process ci-dessus. Nous ajoutons donc un autre rappel #process à appeler après le rappel #process par défaut. Dans notre processus de rappel, nous pouvons changer le rappel #ajax en une fonction qui nous est propre. Les rappels #process sont déclarés dans \ Drupal\file\Element\ManagedFile :: getInfo () . L'ajout d'un autre gestionnaire #process peut donc se faire dans 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';
}
Maintenant, la commande a été créée et attachée à tous les éléments managed_file
. L'étape suivante consiste à remplacer le rappel #ajax par le nôtre, en y ajoutant la commande personnalisée. Examinons la fonction remplacée (doit être placée dans le fichier .module):
/**
* Custom ajax callback for managed files.
*
* Overrides \Drupal\file\Element\ManagedFile\ManagedFile::uploadAjaxCallback()
*
* @see \Drupal\file\Element\ManagedFile\ManagedFile::uploadAjaxCallback
*/
function MODULE_managed_file_ajax_callback(array &$form, FormStateInterface $form_state) {
// Retrieve the original response.
$response = \Drupal\file\Element\ManagedFile\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;
}
La dernière chose à faire est de remplacer la commande ajax d'origine par la nôtre. Cela se fait dans le processus de rappel que nous avons enregistré dans 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;
}
Cela indique Drupal pour remplacer le rappel ajax d'origine par MODULE_managed_file_ajax_callback()
. Ce rappel récupère la réponse du rappel d'origine et ajoute notre commande personnalisée à la fin de celui-ci. La commande est appelée lorsque le fichier a été téléchargé, ce qui nous permet d'exécuter tout JS dont nous avons besoin côté serveur une fois le téléchargement des fichiers terminé.
Sur la base de la réponse @Jaypan et du commentaire @FLY, j'ai fait un FieldWidget. Cela a en effet supprimé le besoin de hook_managed_file_process
et hook_element_info_alter
. Cela a également permis d'utiliser le widget par champ.
Créez d'abord votre FieldWidget personnalisé dans [MODULE]/src/Plugin/Field/FieldWidget
:
namespace Drupal\[MODULE]\Plugin\Field\FieldWidget;
use Drupal\Core\Form\FormStateInterface;
use Drupal\file\Plugin\Field\FieldWidget\FileWidget as CoreFileWidget;
/**
* @FieldWidget(
* id = "[MODULE]_file_widget",
* label = @Translation("[MODULE] file upload widget"),
* field_types = {
* "file"
* }
* )
*/
class FileWidget extends CoreFileWidget {
/**
* Override to replace the upload/file HTML control
* with the [MODULE] form element.
*
*/
public static function process($element, FormStateInterface $form_state, $form) {
$element = parent::process($element, $form_state, $form);
if (!isset($element['upload'])) {
return $element;
}
$element['upload_button']['#ajax']['callback'] = [get_called_class(), '[MODULE]ManagedFileAjaxCallback'];
$element['upload_button']['#attached']['library'][] = '[MODULE]/[MODULE].managed_file_upload_complete_event_command';
return $element;
}
/**
* Custom ajax callback for managed files.
*
* Overrides \Drupal\file\Element\ManagedFile\ManagedFile::uploadAjaxCallback()
*
* @see \Drupal\file\Element\ManagedFile\ManagedFile::uploadAjaxCallback
*/
public function [MODULE]ManagedFileAjaxCallback(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($form, $form_state));
return $response;
}
}
Puis, comme Jaypan l'a déjà écrit, créez un fichier dans [MODULE]/src/Ajax
pour votre commande Ajax personnalisée (Donnez au fichier le nom de votre classe avec l'extension 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',
];
}
}
Créez votre fichier javascript qui est déclenché par la commande dans [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));
Enregistrez votre fichier javascript dans [MODULE].libraries.yml
:
[MODULE].managed_file_upload_complete_event_command:
js:
js/managed_file_upload_complete_event_command.js: {}
dependencies:
- core/jquery
- core/jquery.once
Vous pouvez aussi utiliser $form
et $form_state
dans votre commande personnalisée pour l'utiliser dans votre javascript si vous en avez besoin.
J'ai fini par utiliser des valeurs différentes des fichiers téléchargés. Ma [MODULE]/src/Ajax/ManagedFileUploadCompleteEventCommand.php
ressemble à ça:
namespace Drupal\[MODULE]\Ajax;
use Drupal\Core\Ajax\CommandInterface;
use Drupal\file\Entity\File;
/**
* Command to trigger an event when managed file upload is complete.
*/
class ManagedFileUploadCompleteEventCommand implements CommandInterface {
// Constructs a ReadMessageCommand object.
public function __construct($form) {
$this->form = $form;
}
/**
* Implements Drupal\Core\Ajax\CommandInterface:render().
*/
public function render() {
$files = array();
$form_delta = $this->form['#file_upload_delta'];
for($i=0;$i<$form_delta;$i++) {
$file_id = array_keys($this->form[$i]['#files'])[0];
$file = File::load($file_id);
$uri = $file->getFileUri();
$stream_wrapper_manager = \Drupal::service('stream_wrapper_manager')->getViaUri($uri);
$file_path = $stream_wrapper_manager->getExternalUrl();
$file_name = $file->getFilename();
$files[$i] = array(
'file_id' => $file_id,
'file_path' => $file_path,
'file_name' => $file_name,
);
}
return [
'command' => 'triggerManagedFileUploadComplete',
'files' => $files,
];
}
}
Vous pouvez les utiliser dans le fichier javascript que vous avez créé dans [MODULE]/js/managed_file_upload_complete_event_command.js
comme ça:
(function (Drupal) {
"use strict";
/**
* Add new custom command.
*/
Drupal.AjaxCommands.prototype.triggerManagedFileUploadComplete = function (ajax, response, status) {
var files = response.files;
// Do stuff here after file upload is complete.
console.log(files.file_id);
console.log(files.file_path);
console.log(files.file_name);
};
}(Drupal));