web-dev-qa-db-fra.com

Comment rendre un bouton d'envoi pour qu'il soit désactivé initialement et activé par JavaScript?

Nous avons un formulaire qui comprend un composant frontal React JS. Le composant recueille certaines informations requises, que le composant React fournit ensuite au serveur principal en remplissant un élément masqué). champ de formulaire.

Pour garantir qu'un utilisateur ne peut pas contourner la saisie des données requises via le composant, nous devons satisfaire aux exigences suivantes:

  • Avant que le composant React se charge, le bouton "Soumettre" doit être désactivé. Cela garantit qu'un utilisateur ne peut pas soumettre le formulaire simplement en désactivant JS ou en cliquant simplement sur le bouton avant que le chargement de la page ne se termine .
  • Après le chargement du composant React, chaque fois que les valeurs requises sont effacées, le bouton "Soumettre" doit être désactivé.
  • Une fois les valeurs requises fournies, le bouton "Soumettre" doit être activé.
  • Il ne doit pas être possible de soumettre le formulaire si la valeur masquée est vide.

J'ai essayé de rendre le formulaire comme ceci:

  public function buildForm(array $form, FormStateInterface $form_state) {
    $form = [];

    $form['selected_values'] = [
      '#type' => 'hidden',
    ];

    $form['actions'] = [
      '#type'   => 'actions',
      '#weight' => 100,
    ];

    $form['actions']['submit'] = [
      '#type'         => 'submit',
      '#value'        => $this->t('Submit'),
      '#button_type'  => 'primary',
      '#disabled'     => TRUE, // Controlled by JS on the frontend
    ];

    return $form;
  }

Et puis en JavaScript, j'ai un rappel d'événement onValueChange() qui ressemble à ceci:

function onValueChange(value) {
  if (value.trim() === '') {
    $(submitButton).prop('disabled', true);
  }
  else {
    $(submitButton)
      .prop('disabled', false)
      .removeClass('is-disabled');
  }
}

J'ai aussi essayé:

  public function buildForm(array $form, FormStateInterface $form_state) {
    $form = [];

    $selected_values = form_state->getValue('selected_values');

    $form['selected_values'] = [
      '#type' => 'hidden',
    ];

    $form['actions'] = [
      '#type'   => 'actions',
      '#weight' => 100,
    ];

    $form['actions']['submit'] = [
      '#type'         => 'submit',
      '#value'        => $this->t('Submit'),
      '#button_type'  => 'primary',
      '#disabled'     => !empty($selected_values),
    ];

    return $form;
  }

Ces deux approches présentent le même problème - cliquer sur "Soumettre" ne soumet pas le formulaire; il semble juste reconstruire le formulaire. Avec la deuxième approche, il semble que les valeurs sélectionnées entrantes soient toujours vides pendant le rendu du formulaire; ils ne sont appliqués à l'état du formulaire que après le formulaire est construit.

Comment pouvons-nous nous assurer que le bouton "Soumettre" démarre désactivé, tout en exigeant les valeurs nécessaires?

2
GuyPaddock

L'utilisation de #disabled Sur le bouton d'envoi n'est pas la bonne solution car elle communique à l'API de formulaire Drupal) que le bouton n'attend pas d'entrée. En d'autres termes, cela provoque Drupal pour agir comme si le bouton n'était pas là du tout. Par conséquent, cliquer sur le bouton Soumettre n'a aucun effet. Tout cela est logique du point de vue de la sécurité, car normalement vous ne voudriez pas que les utilisateurs astucieux désactivent la logique de backend en utilisant des outils de développement pour forcer l'activation d'un élément de formulaire désactivé ou en lecture seule.

Au lieu de cela, la solution est double:

  1. Rendre le bouton désactivé via les attributs HTML, de sorte que le bouton est uniquement désactivé dans le navigateur mais est toujours considéré comme un bouton actif sur le backend.
  2. Marquez le champ caché comme '#required' => TRUE Afin qu'il ne soit pas possible de contourner le formulaire même si l'utilisateur est rusé avec Dev Tools.

Voici à quoi cela ressemble en pratique:

  public function buildForm(array $form, FormStateInterface $form_state) {
    $form = [];

    $form['selected_values'] = [
      // Though this field is hidden, it's required. So, the title is still
      // shown if/when the field is ever NULL during submission.
      '#title'    => $this->t('File selection'),
      '#type'     => 'hidden',
      '#required' => TRUE,
    ];

    $form['actions'] = [
      '#type'   => 'actions',
      '#weight' => 100,
    ];

    $form['actions']['submit'] = [
      '#type'         => 'submit',
      '#value'        => $this->t('Submit'),
      '#button_type'  => 'primary',
      '#attributes' => [
        // This button is programmatically enabled/disabled via JS on the
        // frontend.
        'disabled' => 'disabled',
    ];

    return $form;
  }

La logique frontale d'activation du bouton reste la même. La différence ici est que le bouton est uniquement désactivé dans le code HTML rendu plutôt que d'être désactivé dans le formulaire principal. De plus, si un utilisateur astucieux active le bouton en supprimant l'attribut disabled sur l'élément HTML DOM, il se retrouvera simplement avec une erreur de validation qui dit:

Le champ de sélection de fichier est obligatoire.

Séparément, quant à la raison pour laquelle la deuxième approche n'a pas réussi à activer/désactiver le bouton "Soumettre" en fonction des valeurs entrantes, c'est parce que Drupal construit le formulaire avant de traiter les valeurs entrantes afin qu'il sache quels éléments du formulaire doivent être appelés pour traiter l'entrée. Ainsi, au moment où le générateur de formulaire est appelé, il n'y a encore rien dans les valeurs d'état du formulaire. En théorie, il pourrait être possible d'obtenir l'entrée brute via $form_state->getUserInput(), puis contrôler si le bouton doit être activé ou non en fonction de cela, mais cette approche peut avoir des implications en termes de sécurité.

2
GuyPaddock