Comment détecter quand on clique sur Annuler sur l'entrée de fichier?

112

Comment puis-je détecter lorsque l'utilisateur annule une entrée de fichier à l'aide d'une entrée de fichier html?

onChange me permet de détecter quand ils choisissent un fichier, mais j'aimerais aussi savoir quand ils s'annulent (fermez la boîte de dialogue de choix de fichier sans rien sélectionner).

PPP
la source
Vous pouvez également détecter si quelqu'un choisit le fichier, puis essaie de choisir, envoyez-en un mais annulez, vous obtiendrez videe.target.files
jcubic

Réponses:

45

Bien que ce ne soit pas une solution directe, et également mauvaise en ce sens qu'elle fonctionne uniquement (pour autant que j'ai testé) avec onfocus (nécessitant un blocage d'événements assez limitatif), vous pouvez y parvenir avec les éléments suivants:

document.body.onfocus = function(){ /*rock it*/ }

Ce qui est bien à ce sujet, c'est que vous pouvez l'attacher / le détacher à temps avec l'événement de fichier, et cela semble également fonctionner correctement avec les entrées cachées (un avantage certain si vous utilisez une solution de contournement visuelle pour le type d'entrée par défaut merdique = ' fichier'). Après cela, il vous suffit de déterminer si la valeur d'entrée a changé.

Un exemple:

var godzilla = document.getElementById('godzilla')

godzilla.onclick = charge

function charge()
{
    document.body.onfocus = roar
    console.log('chargin')
}

function roar()
{
    if(godzilla.value.length) alert('ROAR! FILES!')
    else alert('*empty wheeze*')
    document.body.onfocus = null
    console.log('depleted')
}

Voyez-le en action: http://jsfiddle.net/Shiboe/yuK3r/6/

Malheureusement, cela ne semble fonctionner que sur les navigateurs Webkit. Peut-être que quelqu'un d'autre peut trouver la solution Firefox / IE

Shiboe
la source
Je ne voulais pas que l'événement de mise au point se déclenche à chaque fois, alors je voulais l'utiliser addEventListener("focus",fn,{once: true}). Cependant, je n'ai pas pu le faire tirer du tout en utilisant document.body.addEventListener... Je ne sais pas pourquoi. Au lieu de cela, j'ai obtenu le même résultat avec window.addEventListener.
WoodenKitty
3
Cela ne fonctionne pas dans Firefox et Edge pour moi. Cependant, écouter l' mousemoveévénement sur window.document en plus d'écouter focussur windowsemble fonctionner partout (au moins dans les navigateurs modernes, je me fiche des épaves des dix dernières années). Fondamentalement, tout événement d'interaction peut être utilisé pour cette tâche qui est bloquée par une boîte de dialogue d'ouverture de fichier ouverte.
John Weisz
@WoodenKitty comment avez-vous implémenté cela en utilisant addEventListener. J'essaie de le faire en angulaire et le document.body ne fonctionne pas non plus pour moi
Terrance Jackson
cela ne fonctionne pas sur les champs de saisie masqués.
Sebastian le
20

Vous ne pouvez pas.

Le résultat de la boîte de dialogue de fichier n'est pas exposé au navigateur.

Oded
la source
Est-il possible de vérifier si le sélecteur de fichiers est ouvert ou non?
Ppp
1
@Ppp - Non. Le navigateur ne vous le dit pas.
Oded le
6
"Le résultat de la boîte de dialogue de fichier n'est pas exposé au navigateur." Ce n'est pas vrai si la boîte de dialogue est fermée en sélectionnant un fichier.
Trevor
3
Le changement déclenché à l'aide de la boîte de dialogue de fichier est exposé au navigateur. Voir, s'il n'y a pas de fichier sélectionné, et aucun fichier n'est sélectionné par la suite, alors rien n'a changé, donc aucun changeévénement n'est distribué.
John Weisz
1
De plus, si un fichier est déjà sélectionné et que vous sélectionnez à nouveau le même fichier, aucun changeévénement n'est distribué pour cela non plus.
Patrick Roberts
16

Je vais donc jeter mon chapeau sur cette question depuis que j'ai trouvé une nouvelle solution. J'ai une application Web progressive qui permet aux utilisateurs de capturer des photos et des vidéos et de les télécharger. Nous utilisons WebRTC lorsque cela est possible, mais revenons aux sélecteurs de fichiers HTML5 pour les appareils avec moins de support * toux Safari toux *. Si vous travaillez spécifiquement sur une application Web mobile Android / iOS qui utilise l'appareil photo natif pour capturer directement des photos / vidéos, c'est la meilleure solution que j'ai rencontrée.

Le nœud de ce problème est que lorsque la page se charge, le fileest null, mais lorsque l'utilisateur ouvre la boîte de dialogue et appuie sur "Annuler", le fileest toujours null, donc il n'a pas "changé", donc aucun événement "changement" n'est déclenché. Pour les ordinateurs de bureau, ce n'est pas si mal, car la plupart des interfaces utilisateur de bureau ne dépendent pas du fait de savoir quand une annulation est invoquée, mais les interfaces utilisateur mobiles qui font apparaître l'appareil photo pour capturer une photo / vidéo dépendent beaucoup de savoir quand une annulation est activée.

J'ai initialement utilisé l' document.body.onfocusévénement pour détecter le retour de l'utilisateur depuis le sélecteur de fichiers, et cela a fonctionné pour la plupart des appareils, mais iOS 11.3 l'a cassé car cet événement n'est pas déclenché.

Concept

Ma solution à cela est * frémir * pour mesurer la synchronisation du processeur pour déterminer si la page est actuellement au premier plan ou en arrière-plan. Sur les appareils mobiles, le temps de traitement est donné à l'application actuellement au premier plan. Lorsqu'une caméra est visible, elle volera du temps CPU et dépriorisera le navigateur. Tout ce que nous avons à faire est de mesurer le temps de traitement accordé à notre page, lorsque la caméra démarre, notre temps disponible diminue considérablement. Lorsque la caméra est rejetée (annulée ou non), notre temps disponible augmente.

la mise en oeuvre

Nous pouvons mesurer la synchronisation du processeur en utilisant setTimeout()pour appeler un rappel en X millisecondes, puis mesurer le temps qu'il a fallu pour l'invoquer. Le navigateur ne l'invoquera jamais exactement après X millisecondes, mais s'il est raisonnablement proche, nous devons être au premier plan. Si le navigateur est très loin (plus de 10 fois plus lent que demandé), nous devons être en arrière-plan. Une implémentation de base de ceci est comme ceci:

function waitForCameraDismiss() {
  const REQUESTED_DELAY_MS = 25;
  const ALLOWED_MARGIN_OF_ERROR_MS = 25;
  const MAX_REASONABLE_DELAY_MS =
      REQUESTED_DELAY_MS + ALLOWED_MARGIN_OF_ERROR_MS;
  const MAX_TRIALS_TO_RECORD = 10;

  const triggerDelays = [];
  let lastTriggerTime = Date.now();

  return new Promise((resolve) => {
    const evtTimer = () => {
      // Add the time since the last run
      const now = Date.now();
      triggerDelays.push(now - lastTriggerTime);
      lastTriggerTime = now;

      // Wait until we have enough trials before interpreting them.
      if (triggerDelays.length < MAX_TRIALS_TO_RECORD) {
        window.setTimeout(evtTimer, REQUESTED_DELAY_MS);
        return;
      }

      // Only maintain the last few event delays as trials so as not
      // to penalize a long time in the camera and to avoid exploding
      // memory.
      if (triggerDelays.length > MAX_TRIALS_TO_RECORD) {
        triggerDelays.shift();
      }

      // Compute the average of all trials. If it is outside the
      // acceptable margin of error, then the user must have the
      // camera open. If it is within the margin of error, then the
      // user must have dismissed the camera and returned to the page.
      const averageDelay =
          triggerDelays.reduce((l, r) => l + r) / triggerDelays.length
      if (averageDelay < MAX_REASONABLE_DELAY_MS) {
        // Beyond any reasonable doubt, the user has returned from the
        // camera
        resolve();
      } else {
        // Probably not returned from camera, run another trial.
        window.setTimeout(evtTimer, REQUESTED_DELAY_MS);
      }
    };
    window.setTimeout(evtTimer, REQUESTED_DELAY_MS);
  });
}

J'ai testé cela sur une version récente d'iOS et d'Android, en faisant apparaître la caméra native en définissant les attributs sur l' <input />élément.

<input type="file" accept="image/*" capture="camera" />
<input type="file" accept="video/*" capture="camcorder" />

Cela fonctionne en fait beaucoup mieux que ce à quoi je m'attendais. Il exécute 10 essais en demandant qu'un minuteur soit appelé en 25 millisecondes. Il mesure ensuite combien de temps il a réellement fallu pour invoquer, et si la moyenne de 10 essais est inférieure à 50 millisecondes, nous supposons que nous devons être au premier plan et que la caméra est partie. Si elle est supérieure à 50 millisecondes, alors nous devons toujours être en arrière-plan et continuer à attendre.

Quelques détails supplémentaires

J'ai utilisé setTimeout()plutôt que setInterval()parce que ce dernier peut mettre en file d'attente plusieurs invocations qui s'exécutent immédiatement les unes après les autres. Cela pourrait augmenter considérablement le bruit dans nos données, donc je suis resté avec setTimeout()même si c'est un peu plus compliqué à faire.

Ces chiffres particuliers ont bien fonctionné pour moi, même si j'ai vu au moins une fois où le rejet de la caméra a été détecté prématurément. Je pense que c'est parce que la caméra peut être lente à s'ouvrir et que l'appareil peut exécuter 10 essais avant de devenir réellement en arrière-plan. L'ajout d'essais supplémentaires ou l'attente de 25 à 50 millisecondes avant de démarrer cette fonction peut être une solution de contournement pour cela.

Bureau

Malheureusement, cela ne fonctionne pas vraiment pour les navigateurs de bureau. En théorie, la même astuce est possible car ils donnent la priorité à la page actuelle par rapport aux pages en arrière-plan. Cependant, de nombreux bureaux disposent de suffisamment de ressources pour que la page continue à fonctionner à pleine vitesse, même en arrière-plan, cette stratégie ne fonctionne donc pas vraiment dans la pratique.

Solutions alternatives

Une solution alternative que peu de gens mentionnent que j'ai explorée était de se moquer d'un FileList. Nous commençons par nulldans <input />et puis si l'utilisateur ouvre la caméra et annule, il revient null, ce qui n'est pas un changement et aucun événement ne se déclenchera. Une solution consisterait à attribuer un fichier factice <input />au début de la page. Par conséquent, définir sur nullserait un changement qui déclencherait l'événement approprié.

Malheureusement, il n'y a aucun moyen officiel de créer un FileList, et l' <input />élément nécessite un FileListen particulier et n'acceptera aucune autre valeur en plus null. Naturellement, les FileListobjets ne peuvent pas être construits directement, à cause d'un vieux problème de sécurité qui n'est même plus pertinent apparemment. Le seul moyen d'en obtenir un en dehors d'un <input />élément est d'utiliser un hack qui copie-colle des données pour FileListsimuler un événement de presse-papiers pouvant contenir un objet (vous simulez essentiellement un glisser-déposer d'un fichier sur -votre-événement de site Web). C'est possible dans Firefox, mais pas pour iOS Safari, donc ce n'était pas viable pour mon cas d'utilisation particulier.

Navigateurs, s'il vous plaît ...

Inutile de dire que c'est manifestement ridicule. Le fait que les pages Web ne reçoivent aucune notification indiquant qu'un élément critique de l'interface utilisateur a changé est tout simplement risible. C'est vraiment un bogue dans la spécification, car il n'a jamais été destiné à une interface utilisateur de capture multimédia en plein écran, et ne pas déclencher l'événement "changement" est techniquement conforme aux spécifications.

Cependant , les fournisseurs de navigateurs peuvent-ils reconnaître la réalité de cela? Cela peut être résolu soit avec un nouvel événement "done" qui est déclenché même lorsqu'aucun changement ne se produit, soit vous pouvez simplement déclencher "change" de toute façon. Ouais, ce serait contraire aux spécifications, mais il est trivial pour moi de déduire un événement de changement du côté JavaScript, mais fondamentalement impossible d'inventer mon propre événement "terminé". Même ma solution n'est vraiment qu'une heuristique, si elle n'offre aucune garantie sur l'état du navigateur.

Dans l'état actuel des choses, cette API est fondamentalement inutilisable pour les appareils mobiles, et je pense qu'un changement de navigateur relativement simple pourrait rendre cela infiniment plus facile pour les développeurs Web * sort de la boîte à savon *.

Douglas Parker
la source
7

Lorsque vous sélectionnez un fichier et cliquez sur ouvrir / annuler, l' inputélément devrait perdre le focus aka blur. En supposant que l'initiale valuede inputest vide, toute valeur non vide dans votre blurgestionnaire indiquerait un OK, et une valeur vide signifierait un Cancel.

UPDATE: Le blurn'est pas déclenché lorsque le inputest masqué. Vous ne pouvez donc pas utiliser cette astuce avec les téléchargements basés sur IFRAME, à moins que vous ne souhaitiez afficher temporairement le fichier input.

zvolkov
la source
3
Dans Firefox 10 bêta et Chrome 16, blurne se déclenche pas lorsque vous sélectionnez un fichier. Je n'ai pas essayé dans d'autres navigateurs.
Blaise
1
Cela ne fonctionne pas - le flou sur l'entrée est déclenché immédiatement après le clic. Chrome 63.0.3239.84 x64 sur Win7.
Qwertiy
7
/* Tested on Google Chrome */
$("input[type=file]").bind("change", function() {
    var selected_file_name = $(this).val();
    if ( selected_file_name.length > 0 ) {
        /* Some file selected */
    }
    else {
        /* No file selected or cancel/close
           dialog button clicked */
        /* If user has select a file before,
           when they submit, it will treated as
           no file selected */
    }
});
Rizky
la source
6
Avez-vous vérifié si votre elsedéclaration est déjà évaluée?
jayarjo
autant que je me souvienne quand j'ai écrit cette réponse, si l'utilisateur sélectionne un fichier puis re-sélectionne le fichier (mais annule ensuite), il sera traité comme aucun fichier sélectionné, j'utilise uniquement alert à la variable selected_file_name pour le tester. de toute façon, je ne l'ai plus testé depuis.
Rizky
4
Je peux dire, après quelques tests sur plusieurs navigateurs, que Firefox ne déclenche pas d'événement de modification lors de l'annulation, ni n'efface les fichiers existants sélectionnés s'ils sont rouverts et annulés cliqués la deuxième fois. Etrange, hein?
mix3d
7

La plupart de ces solutions ne fonctionnent pas pour moi.

Le problème est que vous ne savez jamais quel événement sera déclenché, n'est-ce pas clickou est-ce change? Vous ne pouvez assumer aucun ordre, car cela dépend probablement de l'implémentation du navigateur.

Au moins dans Opera et Chrome (fin 2015) clickest déclenché juste avant de `` remplir '' l'entrée avec des fichiers, vous ne saurez donc jamais la durée files.length != 0jusqu'à ce que vous retardiez clickd'être déclenché après change.

Voici le code:

var inputfile = $("#yourid");

inputfile.on("change click", function(ev){
    if (ev.originalEvent != null){
        console.log("OK clicked");
    }
    document.body.onfocus = function(){
        document.body.onfocus = null;
        setTimeout(function(){
            if (inputfile.val().length === 0) console.log("Cancel clicked");
        }, 1000);
    };
});
superdario128
la source
Si vous cliquez sur la page ... "Annuler cliqué" ... = | (Testé avec Chrome)
Eduardo Lucio
5

Écoutez également l'événement de clic.

À la suite de l'exemple de Shiboe, voici un exemple jQuery:

var godzilla = $('#godzilla');
var godzillaBtn = $('#godzilla-btn');

godzillaBtn.on('click', function(){
    godzilla.trigger('click');
});

godzilla.on('change click', function(){

    if (godzilla.val() != '') {
        $('#state').html('You have chosen a Mech!');    
    } else {
        $('#state').html('Choose your Mech!');
    }

});

Vous pouvez le voir en action ici: http://jsfiddle.net/T3Vwz

AEQ
la source
1
J'ai testé sur Chrome 51, cela ne fonctionne pas la première fois que vous sélectionnez des fichiers. La solution est d'ajouter un délai: jsfiddle.net/7jfbmunh
Benoit Blanchon
Votre violon n'a pas fonctionné pour moi - avec Firefox 58.0.2
barrypicker
4

Vous pouvez attraper l'annulation si vous choisissez le même fichier que précédemment et que vous cliquez sur annuler: dans ce cas.

Vous pouvez le faire comme ceci:

<input type="file" id="myinputfile"/>
<script>
document.getElementById('myinputfile').addEventListener('change', myMethod, false);
function myMethod(evt) {
  var files = evt.target.files; 
  f= files[0];
  if (f==undefined) {
     // the user has clicked on cancel
  }
  else if (f.name.match(".*\.jpg")|| f.name.match(".*\.png")) {
     //.... the user has choosen an image file
     var reader = new FileReader();
     reader.onload = function(evt) { 
        try {
        myimage.src=evt.target.result;
            ...
         } catch (err) {
            ...
         }
     };
  }     
  reader.readAsDataURL(f);          
</script>
loveJs
la source
1
Thnkx @loveJs ce message m'a vraiment aidé.
VPK
4
Pas exactement, si l'utilisateur sélectionne le même fichier, ce n'est pas une annulation = (
Tiago Peres França
13
onchange ne se déclenche qu'en cas de changement. Donc, si le fichier est le même fichier que le précédent, l'écouteur onchange ne se déclenchera pas.
Shane
1
Vous pouvez effacer la sélection d'entrée (input.value = '') après avoir détecté le premier changement, de sorte que la deuxième fois onchange se déclenchera même si l'utilisateur sélectionne à nouveau le même fichier.
Chris
1
@CodeGems, il est permis de définir par programme input.value sur une chaîne vide. Mais vous avez raison de dire que le définir sur une autre valeur n'est pas autorisé.
Chris
4

Le moyen le plus simple est de vérifier s'il y a des fichiers dans la mémoire temporaire. Si vous souhaitez obtenir l'événement de modification chaque fois que l'utilisateur clique sur l'entrée de fichier, vous pouvez le déclencher.

var yourFileInput = $("#yourFileInput");

yourFileInput.on('mouseup', function() {
    $(this).trigger("change");
}).on('change', function() {
    if (this.files.length) {
        //User chose a picture
    } else {
        //User clicked cancel
    }
});
Krzysztof Janiszewski
la source
2

La solution de Shiboe serait bonne si elle fonctionnait sur le webkit mobile, mais ce n'est pas le cas. Ce que je peux proposer, c'est d'ajouter un écouteur d'événement mousemove à un objet dom au moment où la fenêtre d'entrée de fichier est ouverte, comme ceci:

 $('.upload-progress').mousemove(function() {
        checkForFiles(this);
      });
      checkForFiles = function(me) {
        var filefield = $('#myfileinput');
        var files = filefield.get(0).files;
        if (files == undefined || files[0] == undefined) $(me).remove(); // user cancelled the upload
      };

L'événement mousemove est bloqué de la page pendant que la boîte de dialogue de fichier est ouverte, et quand elle est fermée, on vérifie s'il y a des fichiers dans l'entrée de fichier. Dans mon cas, je veux un indicateur d'activité bloquant les choses jusqu'à ce que le fichier soit téléchargé, donc je veux uniquement supprimer mon indicateur lors de l'annulation.

Cependant, cela ne résout pas pour le mobile, car il n'y a pas de souris à déplacer. Ma solution est loin d'être parfaite, mais je pense qu'elle est assez bonne.

       $('.upload-progress').bind('touchstart', function() {
        checkForFiles(this);
      });

Maintenant, nous écoutons une touche sur l'écran pour faire la même vérification des fichiers. Je suis assez confiant que le doigt de l'utilisateur sera mis sur l'écran assez rapidement après l'annulation et le rejet de cet indicateur d'activité.

On pourrait également simplement ajouter l'indicateur d'activité sur l'événement de changement d'entrée de fichier, mais sur mobile, il y a souvent un décalage de quelques secondes entre la sélection de l'image et le déclenchement de l'événement de changement, donc c'est juste une bien meilleure UX pour l'indicateur d'activité à afficher sur le début du processus.

rançon
la source
2

J'ai trouvé cet attribut, le plus simple à ce jour.

if ($('#selectedFile')[0].files.length > 1)
{
   // Clicked on 'open' with file
} else {
   // Clicked on 'cancel'
}

Ici, selectedFileest un input type=file.

Israël
la source
C'est une bonne solution pour moi, sauf que ça devrait être> = 1
Bron Thulke
2

Je sais que c'est une question très ancienne, mais juste au cas où cela aiderait quelqu'un, j'ai trouvé en utilisant l'événement onmousemove pour détecter l'annulation, qu'il était nécessaire de tester deux ou plusieurs événements de ce type dans un court laps de temps. Cela était dû au fait que des événements onmousemove uniques sont générés par le navigateur (Chrome 65) chaque fois que le curseur est déplacé hors de la fenêtre de dialogue de sélection de fichier et chaque fois qu'il est déplacé hors de la fenêtre principale et de retour. Un simple compteur d'événements de mouvement de la souris couplé à un délai d'expiration de courte durée pour remettre le compteur à zéro a fonctionné un régal.

Steve Brooker
la source
1
Le premier paragraphe est une bonne réponse, même s'il peut être divisé en plusieurs pour une meilleure lisibilité. Mais ces deux lignes "SOAPBOX" la rendent à la limite d'une non-réponse car les questions n'appartiennent pas aux réponses. Pouvez-vous reformuler / reformuler un peu votre réponse? Je vous remercie.
Filnor
1

Dans mon cas, j'ai dû masquer le bouton de soumission pendant que les utilisateurs sélectionnaient des images.

Voici ce que je viens de dire:

$(document).on('click', '#image-field', function(e) {
  $('.submit-button').prop('disabled', true)
})
$(document).on('focus', '#image-field'), function(e) {
  $('.submit-button').prop('disabled', false)
})

#image-fieldest mon sélecteur de fichiers. Lorsque quelqu'un clique dessus, je désactive le bouton d'envoi du formulaire. Le fait est que lorsque la boîte de dialogue de fichier s'est fermée - peu importe qu'ils sélectionnent un fichier ou s'annulent - #image-fieldle focus est revenu, alors j'écoute cet événement.

METTRE À JOUR

J'ai trouvé que cela ne fonctionne pas en safari et poltergeist / phantomjs. Tenez compte de ces informations si vous souhaitez l'implémenter.

bonyiii
la source
1

En combinant les solutions de Shiboe et d'alx, j'ai le code le plus fiable:

var selector = $('<input/>')
   .attr({ /* just for example, use your own attributes */
      "id": "FilesSelector",
      "name": "File",
      "type": "file",
      "contentEditable": "false" /* if you "click" on input via label, this prevents IE7-8 from just setting caret into file input's text filed*/
   })
   .on("click.filesSelector", function () {
      /* do some magic here, e.g. invoke callback for selection begin */
      var cancelled = false; /* need this because .one calls handler once for each event type */
      setTimeout(function () {
         $(document).one("mousemove.filesSelector focusin.filesSelector", function () {
            /* namespace is optional */
            if (selector.val().length === 0 && !cancelled) {
               cancelled = true; /* prevent double cancel */
               /* that's the point of cancel,   */
            }
         });                    
      }, 1); /* 1 is enough as we just need to delay until first available tick */
   })
   .on("change.filesSelector", function () {
      /* do some magic here, e.g. invoke callback for successful selection */
   })
   .appendTo(yourHolder).end(); /* just for example */

Généralement, l'événement mousemove fait l'affaire, mais au cas où l'utilisateur aurait fait un clic et que:

  • annulé la boîte de dialogue d'ouverture de fichier par la touche d'échappement (sans déplacer la souris), fait un autre clic précis pour ouvrir à nouveau la boîte de dialogue de fichier ...
  • mis le focus sur n'importe quelle autre application, puis est revenu à la boîte de dialogue d'ouverture de fichier du navigateur et l'a fermé, puis ouvert à nouveau via la touche entrée ou espace ...

... nous n'obtiendrons pas d'événement mousemove, donc pas de rappel d'annulation. De plus, si l'utilisateur annule la deuxième boîte de dialogue et déplace la souris, nous obtiendrons 2 rappels d'annulation. Heureusement, les événements spéciaux jQuery focusIn remontent au document dans les deux cas, ce qui nous aide à éviter de telles situations. La seule limitation est si l'on bloque l'événement focusIn non plus.

KGA
la source
J'ai attrapé l' mousemoveévénement documentlors de la sélection de fichiers dans la boîte de dialogue du système d'exploitation dans Chrome 56.0.2924.87 sur Ubuntu 16.10. Pas de problème, lorsque vous ne déplacez pas la souris ou que vous n'utilisez que le clavier pour sélectionner le fichier. J'ai dû remplacer mousemovepar keydownpour détecter l'annulation le plus tôt possible, mais pas "trop ​​tôt", alors que la boîte de dialogue était encore ouverte.
Ferdinand Prantl
1

Je vois que ma réponse serait assez dépassée, mais jamais moins. J'ai fait face au même problème. Alors voici ma solution. Le code le plus utile était celui de KGA. Mais cela ne fonctionne pas totalement et c'est un peu compliqué. Mais je l'ai simplifié.

En outre, le principal problème était que l'événement de «changement» ne survient pas instantanément après la mise au point, nous devons donc attendre un certain temps.

"#appendfile" - sur lequel l'utilisateur clique pour ajouter un nouveau fichier. Les Hrefs reçoivent des événements de focus.

$("#appendfile").one("focusin", function () {
    // no matter - user uploaded file or canceled,
    // appendfile gets focus
    // change doesn't come instantly after focus, so we have to wait for some time
    // wrapper represents an element where a new file input is placed into
    setTimeout(function(){
        if (wrapper.find("input.fileinput").val() != "") {
            // user has uploaded some file
            // add your logic for new file here
        }
        else {
            // user canceled file upload
            // you have to remove a fileinput element from DOM
        }
    }, 900);
});
Oleksandr Khryplyvenko
la source
1

Vous ne pouvez le détecter que dans des circonstances limitées. Plus précisément, dans Chrome, si un fichier a été sélectionné plus tôt et que la boîte de dialogue de fichier est cliquée et annulée, Chrome efface le fichier et déclenche l'événement onChange.

https://code.google.com/p/chromium/issues/detail?id=2508

Dans ce scénario, vous pouvez le détecter en gérant l'événement onChange et en vérifiant la propriété files .

Kunal
la source
1

Ce qui suit semble fonctionner pour moi (sur le bureau, Windows):

var openFile = function (mimeType, fileExtension) {
    var defer = $q.defer();
    var uploadInput = document.createElement("input");
    uploadInput.type = 'file';
    uploadInput.accept = '.' + fileExtension + ',' + mimeType;

    var hasActivated = false;

    var hasChangedBeenCalled = false;
    var hasFocusBeenCalled = false;
    var focusCallback = function () {
        if (hasActivated) {
            hasFocusBeenCalled = true;
            document.removeEventListener('focus', focusCallback, true);
            setTimeout(function () {
                if (!hasChangedBeenCalled) {
                    uploadInput.removeEventListener('change', changedCallback, true);
                    defer.resolve(null);
                }
            }, 300);
        }
    };

    var changedCallback = function () {
        uploadInput.removeEventListener('change', changedCallback, true);
        if (!hasFocusBeenCalled) {
            document.removeEventListener('focus', focusCallback, true);
        }
        hasChangedBeenCalled = true;
        if (uploadInput.files.length === 1) {
            //File picked
            var reader = new FileReader();
            reader.onload = function (e) {
                defer.resolve(e.target.result);
            };
            reader.readAsText(uploadInput.files[0]);
        }
        else {
            defer.resolve(null);
        }
    };
    document.addEventListener('focus', focusCallback, true); //Detect cancel
    uploadInput.addEventListener('change', changedCallback, true); //Detect when a file is picked
    uploadInput.click();
    hasActivated = true;
    return defer.promise;
}

Cela utilise angularjs $ q mais vous devriez pouvoir le remplacer par n'importe quel autre framework de promesse si nécessaire.

Testé sur IE11, Edge, Chrome, Firefox, mais il ne semble pas fonctionner sur Chrome sur une tablette Android car il ne déclenche pas l'événement Focus.

Peter
la source
1

Le filechamp -type, de manière frustrante, ne répond pas à beaucoup d'événements (le flou serait bien). Je vois beaucoup de gens suggérer des changesolutions orientées et être critiqués.

changefonctionne, mais il a un défaut majeur (par rapport à ce que nous voulons qu'il se passe).

Lorsque vous chargez fraîchement une page contenant un champ de fichier, ouvrez la boîte et appuyez sur Annuler. Rien, frustrant, ne change .

Ce que j'ai choisi de faire est de charger dans un état fermé.

  • La partie suivante du formulaire a section#after-imagedans mon cas est masquée à la vue. Lorsque mes file fieldmodifications, un bouton de téléchargement s'affiche. Une fois le téléchargement réussi, section#after-images'affiche.
  • Si l'utilisateur charge, ouvre la boîte de dialogue de fichier, puis annule, il ne voit jamais le bouton de téléchargement.
  • Si l'utilisateur choisit un fichier, le bouton de téléchargement s'affiche. S'ils ouvrent ensuite la boîte de dialogue et annulent, l' changeévénement est déclenché par cette annulation, et là je peux (et je fais) re-cacher mon bouton de téléchargement jusqu'à ce qu'un fichier approprié soit sélectionné.

J'ai eu la chance que cet état fermé soit déjà la conception de ma forme. Vous n'avez pas besoin d'utiliser le même style, simplement avoir le bouton de téléchargement initialement masqué et lors du changement, définir un champ masqué ou une variable javascript sur quelque chose que vous pouvez surveiller lors de la soumission.

J'ai essayé de changer la valeur des fichiers [0] avant que le champ n'interagisse. Cela n'a rien fait concernant onchange.

Alors oui, ça changemarche, au moins aussi bien que ce que nous allons obtenir. Le champ de fichiers est sécurisé, pour des raisons évidentes, mais à la frustration des développeurs bien intentionnés.

Cela ne correspond pas à mon objectif, mais vous pourrez peut-être onclick, charger une invite d'avertissement (pas une alert(), car cela bloque le traitement des pages), et la masquer si un changement est déclenché et que les fichiers [0] sont nuls. Si le changement n'est pas déclenché, le div reste dans son état.

Joe régulier
la source
0

Il existe un moyen malveillant de le faire (ajouter des rappels ou résoudre une implémentation différée / promise au lieu d' alert()appels):

var result = null;

$('<input type="file" />')
    .on('change', function () {
        result = this.files[0];
        alert('selected!');
    })
    .click();

setTimeout(function () {
    $(document).one('mousemove', function () {
        if (!result) {
            alert('cancelled');
        }
    });
}, 1000);

Fonctionnement: lorsque la boîte de dialogue de sélection de fichier est ouverte, le document ne reçoit pas les événements du pointeur de la souris. Il y a un délai de 1000 ms pour permettre à la boîte de dialogue d'apparaître et de bloquer la fenêtre du navigateur. Enregistré dans Chrome et Firefox (Windows uniquement).

Mais ce n'est pas un moyen fiable de détecter les dialogues annulés, bien sûr. Cependant, cela pourrait améliorer certains comportements de l'interface utilisateur pour vous.

alx
la source
Un inconvénient majeur est sur les téléphones et les tablettes, ils n'utilisent pas de souris normalement!
Peter le
0

Voici ma solution, en utilisant le focus d'entrée de fichier (sans utiliser de minuteries)

var fileInputSelectionInitiated = false;

function fileInputAnimationStart() {
    fileInputSelectionInitiated = true;
    if (!$("#image-selector-area-icon").hasClass("fa-spin"))
        $("#image-selector-area-icon").addClass("fa-spin");
    if (!$("#image-selector-button-icon").hasClass("fa-spin"))
        $("#image-selector-button-icon").addClass("fa-spin");
}

function fileInputAnimationStop() {
    fileInputSelectionInitiated = false;
    if ($("#image-selector-area-icon").hasClass("fa-spin"))
        $("#image-selector-area-icon").removeClass("fa-spin");
    if ($("#image-selector-button-icon").hasClass("fa-spin"))
        $("#image-selector-button-icon").removeClass("fa-spin");
}

$("#image-selector-area-wrapper").click(function (e) {
    $("#fileinput").focus();
    $("#fileinput").click();
});

$("#preview-image-wrapper").click(function (e) {
    $("#fileinput").focus();
    $("#fileinput").click();
});

$("#fileinput").click(function (e) {
    fileInputAnimationStart();
});

$("#fileinput").focus(function (e) {
    fileInputAnimationStop();
});

$("#fileinput").change(function(e) {
    // ...
}

Yovav
la source
L'exécution de votre extrait me donneError: { "message": "Uncaught SyntaxError: missing ) after argument list", "filename": "https://stacksnippets.net/js", "lineno": 51, "colno": 1 }
connexo
0

C'est au mieux piraté, mais voici un exemple fonctionnel de ma solution pour détecter si un utilisateur a téléchargé un fichier ou non, et ne lui permettre de continuer que s'il a téléchargé un fichier.

Fondamentalement , le cacher Continue, Save, Proceedou quel que soit votre bouton est. Ensuite, dans le JavaScript, vous saisissez le nom du fichier. Si le nom de fichier n'a pas de valeur, n'affichez pas le Continuebouton. S'il a une valeur, affichez le bouton. Cela fonctionne également s'ils téléchargent d'abord un fichier, puis essaient de télécharger un autre fichier et cliquent sur Annuler.

Voici le code.

HTML:

<div class="container">
<div class="row">
    <input class="file-input" type="file" accept="image/*" name="fileUpload" id="fileUpload" capture="camera">
    <label for="fileUpload" id="file-upload-btn">Capture or Upload Photo</label>
</div>
<div class="row padding-top-two-em">
    <input class="btn btn-success hidden" id="accept-btn" type="submit" value="Accept & Continue"/>
    <button class="btn btn-danger">Back</button>
</div></div>

JavaScript:

$('#fileUpload').change(function () {
    var fileName = $('#fileUpload').val();
    if (fileName != "") {
        $('#file-upload-btn').html(fileName);
        $('#accept-btn').removeClass('hidden').addClass('show');
    } else {
        $('#file-upload-btn').html("Upload File");
        $('#accept-btn').addClass('hidden');
    }
});

CSS:

.file-input {
    width: 0.1px;
    height: 0.1px;
    opacity: 0;
    overflow: hidden;
    position: absolute;
    z-index: -1; 
}

.file-input + label {
    font-size: 1.25em;
    font-weight: normal;
    color: white;
    background-color: blue;
    display: inline-block;
    padding: 5px;
}

.file-input:focus + label,
.file-input + label:hover {
    background-color: red;
}

.file-input + label {
    cursor: pointer;
}

.file-input + label * {
    pointer-events: none;
}

Pour le CSS, cela consiste en grande partie à rendre le site Web et le bouton accessibles à tous. Personnalisez votre bouton comme vous le souhaitez.

maxshuty
la source
0

Eh bien, cela ne répond pas exactement à votre question. Mon hypothèse est que, vous avez un scénario, lorsque vous ajoutez une entrée de fichier et invoquez la sélection de fichier, et si l'utilisateur frappe l'annulation, vous supprimez simplement l'entrée.

Si tel est le cas, alors: Pourquoi ajouter une entrée de fichier vide?

Créez celui-ci à la volée, mais ajoutez-le au DOM uniquement lorsqu'il est rempli. Comme ceci:

    var fileInput = $("<input type='file' name='files' style='display: none' />");
    fileInput.bind("change", function() {
        if (fileInput.val() !== null) {
            // if has value add it to DOM
            $("#files").append(fileInput);
        }
    }).click();

Donc ici, je crée <input type = "file" /> à la volée, je me lie à son événement de changement et j'appelle immédiatement click. Lors d'un changement, il ne se déclenchera que lorsque l'utilisateur sélectionne un fichier et clique sur Ok, sinon l'entrée ne sera pas ajoutée au DOM, donc ne sera pas soumise.

Exemple de travail ici: https://jsfiddle.net/69g0Lxno/3/

Alexey
la source
0

// Utiliser le survol au lieu du flou

var fileInput = $("#fileInput");

if (fileInput.is(":hover") { 

    //open
} else {

}
jsil
la source
0

Si vous avez déjà besoin de JQuery, cette solution pourrait faire le travail (c'est exactement le même code dont j'avais réellement besoin dans mon cas, bien que l'utilisation d'une promesse consiste simplement à forcer le code à attendre que la sélection de fichier soit résolue):

await new Promise(resolve => {
    const input = $("<input type='file'/>");
    input.on('change', function() {
        resolve($(this).val());
    });
    $('body').one('focus', '*', e => {
        resolve(null);
        e.stopPropagation();
    });
    input.click();

});

user2397355
la source
0

Il existe plusieurs solutions proposées dans ce fil et cette difficulté à détecter lorsque l'utilisateur clique sur le bouton "Annuler" de la boîte de sélection de fichiers est un problème qui affecte de nombreuses personnes.

Le fait est qu'il n'existe pas de moyen fiable à 100% pour détecter si l'utilisateur a cliqué sur le bouton "Annuler" de la boîte de sélection de fichiers. Mais il existe des moyens de détecter de manière fiable si l'utilisateur a ajouté un fichier au fichier d'entrée. Voilà donc la stratégie de base de cette réponse!

J'ai décidé d'ajouter cette réponse car apparemment les autres réponses ne fonctionnent pas sur la plupart des navigateurs ou sont garanties sur les appareils mobiles.

En bref, le code est basé sur 3 points:

  1. Le fichier d'entrée est initialement créé dynamiquement en "mémoire" dans js (nous ne l'ajoutons pas au "HTML" pour le moment);
  2. Après avoir ajouté le fichier, le fichier d'entrée est ajouté au HTML, sinon rien ne se passe;
  3. La suppression du fichier se fait en supprimant le fichier d'entrée du HTML par un événement spécifique, ce qui signifie que «l'édition» / «modification» du fichier se fait en supprimant l'ancien fichier d'entrée et en en créant un nouveau.

Pour une meilleure compréhension, regardez également le code ci-dessous et les notes.

[...]
<button type="button" onclick="addIptFl();">ADD INPUT FILE!</button>
<span id="ipt_fl_parent"></span>
[...]
function dynIptFl(jqElInst, funcsObj) {
    if (typeof funcsObj === "undefined" || funcsObj === "") {
        funcsObj = {};
    }
    if (funcsObj.hasOwnProperty("before")) {
        if (!funcsObj["before"].hasOwnProperty("args")) {
            funcsObj["before"]["args"] = [];
        }
        funcsObj["before"]["func"].apply(this, funcsObj["before"]["args"]);
    }
    var jqElInstFl = jqElInst.find("input[type=file]");

    // NOTE: Open the file selection box via js. By Questor
    jqElInstFl.trigger("click");

    // NOTE: This event is triggered if the user selects a file. By Questor
    jqElInstFl.on("change", {funcsObj: funcsObj}, function(e) {

        // NOTE: With the strategy below we avoid problems with other unwanted events
        // that may be associated with the DOM element. By Questor
        e.preventDefault();

        var funcsObj = e.data.funcsObj;
        if (funcsObj.hasOwnProperty("after")) {
            if (!funcsObj["after"].hasOwnProperty("args")) {
                funcsObj["after"]["args"] = [];
            }
            funcsObj["after"]["func"].apply(this, funcsObj["after"]["args"]);
        }
    });

}

function remIptFl() {

    // NOTE: Remove the input file. By Questor
    $("#ipt_fl_parent").empty();

}

function addIptFl() {

    function addBefore(someArgs0, someArgs1) {
    // NOTE: All the logic here happens just before the file selection box opens.
    // By Questor

        // SOME CODE HERE!

    }

    function addAfter(someArgs0, someArgs1) {
    // NOTE: All the logic here happens only if the user adds a file. By Questor

        // SOME CODE HERE!

        $("#ipt_fl_parent").prepend(jqElInst);

    }

    // NOTE: The input file is hidden as all manipulation must be done via js.
    // By Questor
    var jqElInst = $('\
<span>\
    <button type="button" onclick="remIptFl();">REMOVE INPUT FILE!</button>\
    <input type="file" name="input_fl_nm" style="display: block;">\
</span>\
');

    var funcsObj = {
        before: {
            func: addBefore,
            args: [someArgs0, someArgs1]
        },
        after: {
            func: addAfter,

            // NOTE: The instance with the input file ("jqElInst") could be passed
            // here instead of using the context of the "addIptFl()" function. That
            // way "addBefore()" and "addAfter()" will not need to be inside "addIptFl()",
            // for example. By Questor
            args: [someArgs0, someArgs1]

        }
    };
    dynIptFl(jqElInst, funcsObj);

}

Merci! = D

Eduardo Lucio
la source
0

Nous avons réalisé en angulaire comme ci-dessous.

    1. événement de clic lié sur le fichier de type d'entrée.
      1. Attachez l'événement de focus à la fenêtre et ajoutez une condition si uploadPanel est vrai, puis affichez la console.
      2. lorsque vous cliquez sur le fichier de type d'entrée, la valeur booléenne uploadPanel est true. et la boîte de dialogue apparaissent.
      3. lorsque le bouton Annuler OU Echap cliquez sur puis la boîte de dialogue du dispensaire et de la console apparaissent.

HTML

 <input type="file" formControlName="FileUpload" click)="handleFileInput($event.target.files)" />
          />

TS

this.uploadPanel = false;
handleFileInput(files: FileList) {
    this.fileToUpload = files.item(0);
    console.log("ggg" + files);
    this.uploadPanel = true;
  }



@HostListener("window:focus", ["$event"])
  onFocus(event: FocusEvent): void {
    if (this.uploadPanel == true) {
        console.log("cancel clicked")
        this.addSlot
        .get("FileUpload")
        .setValidators([
          Validators.required,
          FileValidator.validate,
          requiredFileType("png")
        ]);
      this.addSlot.get("FileUpload").updateValueAndValidity();
    }
  }
Mayank Sudden
la source
0

Ajoutez simplement un écouteur 'change' sur votre entrée dont le type est fichier. c'est à dire

<input type="file" id="file_to_upload" name="file_to_upload" />

J'ai fini d'utiliser jQuery et évidemment tout le monde peut utiliser valina JS (selon l'exigence).

$("#file_to_upload").change(function() {

            if (this.files.length) {

            alert('file choosen');

            } else {

            alert('file NOT choosen');

            }

    });
Naveed Ali
la source
.change()n'est pas appelé de manière fiable si l'utilisateur clique sur "annuler" sur le sélecteur de fichiers.
Ben Wheeler le
@benWheeler, je l'ai vérifié sur Chrome et Firefox et cela a fonctionné mais pas testé sur d'autres navigateurs.
Naveed Ali le
0
    enter code here
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <h1>Hello</h1>
    <div id="cancel01">
      <button>Cancel</button>
    </div>

    <div id="cancel02">
      <button>Cancel</button>
    </div>

    <div id="cancel03">
      <button>Cancel</button>
    </div>

    <form>
      <input type="file" name="name" placeholder="Name" />
    </form>

    <script>
      const nameInput = document.querySelector('input[type="file"]');

      /******
       *The below code if for How to detect when cancel is clicked on file input
       ******/
      nameInput.addEventListener('keydown', e => {
        /******
         *If the cancel button is clicked,then you should change the input file value to empty
         ******/

        if (e.key == 'Backspace' || e.code == 'Backspace' || e.keyCode == 8) {
          console.log(e);

          /******
           *The below code will delete the file path
           ******/
          nameInput.value = '';
        }
      });
    </script>
  </body>
</html>
Emmanuel Onah
la source
Les réponses basées uniquement sur le code sont considérées comme de mauvaise qualité: assurez-vous de fournir une explication de ce que fait votre code et de la manière dont il résout le problème. Cela aidera le demandeur et les futurs lecteurs si vous pouvez ajouter plus d'informations dans votre message. Voir Expliquer des réponses entièrement basées sur des codes
Calos
0

Solution pour la sélection de fichiers avec entrée masquée

Remarque: ce code ne détecte pas l'annulation, il offre un moyen de contourner le besoin de le détecter dans un cas courant où les gens essaient de le détecter.

Je suis arrivé ici en cherchant une solution pour les téléchargements de fichiers en utilisant une entrée cachée, je pense que c'est la raison la plus courante pour rechercher un moyen de détecter l'annulation de l'entrée de fichier (ouvrir la boîte de dialogue de fichier -> si un fichier a été sélectionné, exécutez-en code, sinon ne rien faire), voici ma solution:

var fileSelectorResolve;
var fileSelector = document.createElement('input');
fileSelector.setAttribute('type', 'file');

fileSelector.addEventListener('input', function(){
   fileSelectorResolve(this.files[0]);
   fileSelectorResolve = null;
   fileSelector.value = '';
});

function selectFile(){
   if(fileSelectorResolve){
      fileSelectorResolve();
      fileSelectorResolve = null;
   }
   return new Promise(function(resolve){
      fileSelectorResolve = resolve;
      fileSelector.dispatchEvent(new MouseEvent('click'));
   });
}

Exemple d'utilisation:

Notez que si aucun fichier n'a été sélectionné alors la première ligne ne retournera qu'une seule fois selectFile()est appelée à nouveau (ou si vous avez appelé fileSelectorResolve()depuis un autre endroit).

async function logFileName(){
   const file = await selectFile();
   if(!file) return;
   console.log(file.name);
}

Un autre exemple:

async function uploadFile(){
   const file = await selectFile();
   if(!file) return;

   // ... make an ajax call here to upload the file ...
}
Patate
la source
Dans Chrome 83.0, je ne reçois aucun rappel pour l'événement d'entrée lorsque l'utilisateur annule, et pas non plus lors du 2ème essai
Soylent Graham
1
@SoylentGraham Oui, parce que ce code ne détecte pas l'annulation, il offre un moyen de contourner le besoin de le détecter dans un cas courant où les gens essaient de le détecter. Comme je n'étais pas clair à ce sujet, j'ajoute ce commentaire à ma réponse.
pomme de terre
Mon mauvais je pense, j'ai mal lu votre réponse car "il vous dit que l'utilisateur a annulé le deuxième essai"
Soylent Graham
-1

Vous pouvez créer un écouteur de changement jquery sur le champ de saisie et détecter que l'utilisateur annule ou ferme la fenêtre de téléchargement par la valeur du champ.

Voici un exemple:

//when upload button change
$('#upload_btn').change(function(){
    //get uploaded file
    var file = this.files[0];

    //if user choosed a file
    if(file){
        //upload file or perform your desired functiuonality
    }else{
        //user click cancel or close the upload window
    }
});
Mohamed Hana
la source
Je ne sais pas pourquoi ce vote est défavorable - c'est ce que je recherchais. Je vous remercie! 🙂
user1274820