¿Cómo detectar cuándo se hace clic en cancelar en la entrada del archivo?


112

¿Cómo puedo detectar cuando el usuario cancela una entrada de archivo usando una entrada de archivo html?

onChange me permite detectar cuándo eligen un archivo, pero también me gustaría saber cuándo cancelan (cierre el cuadro de diálogo de selección de archivo sin seleccionar nada).


También puede detectar si alguien elige el archivo y luego intenta elegir enviar uno pero cancelar, se quedará vacíoe.target.files
jcubic

Respuestas:


45

Si bien no es una solución directa, y también es mala porque solo (por lo que he probado) funciona con onfocus (que requiere un bloqueo de eventos bastante limitante), puede lograrlo con lo siguiente:

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

Lo bueno de esto es que puede adjuntarlo / separarlo a tiempo con el evento de archivo, y también parece funcionar bien con entradas ocultas (una ventaja definitiva si está utilizando una solución visual para el tipo de entrada predeterminado de mierda = ' expediente'). Después de eso, solo necesita averiguar si el valor de entrada cambió.

Un ejemplo:

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')
}

Véalo en acción: http://jsfiddle.net/Shiboe/yuK3r/6/

Lamentablemente, solo parece funcionar en navegadores webkit. Tal vez alguien más pueda descubrir la solución firefox / IE


No quería que el evento de enfoque se disparara siempre, así que quería usar addEventListener("focus",fn,{once: true}). Sin embargo, no pude hacer que se disparara usando document.body.addEventListener... No sé por qué. En cambio, obtuve el mismo resultado con window.addEventListener.
WoodenKitty

3
Esto no funciona en Firefox y Edge para mí. Sin embargo, escuchando el mousemovecaso de window.document , además de escuchar focusel windowparece funcionar en todas partes (al menos en los navegadores modernos, no me importa acerca de los pecios más allá de década). Básicamente, cualquier evento de interacción se puede utilizar para esta tarea que esté bloqueado por un diálogo de apertura de archivo abierto.
John Weisz

@WoodenKitty, ¿cómo implementaste esto usando addEventListener? Estoy tratando de hacer esto en angular y el document.body tampoco funciona para mí
Terrance Jackson

esto no funciona en campos de entrada ocultos.
Sebastian

20

No puedes.

El resultado del cuadro de diálogo del archivo no se expone al navegador.


¿Es posible comprobar si el selector de archivos está abierto o no?
Ppp

1
@Ppp - No. El navegador no te dice eso.
Oded el

6
"El resultado del cuadro de diálogo del archivo no se expone al navegador". Esto no es cierto si el diálogo se cierra seleccionando un archivo.
Trevor

3
El cambio desencadenado mediante el diálogo de archivo se expone al navegador. Vea, si no hay ningún archivo seleccionado, y no se selecciona ningún archivo después, nada ha cambiado, por lo que no changese envía ningún evento.
John Weisz

1
Además, si un archivo ya está seleccionado y vuelve a seleccionar el mismo archivo, tampoco changese envía ningún evento para ese archivo .
Patrick Roberts

16

Así que me meteré en esta pregunta ya que se me ocurrió una solución novedosa. Tengo una aplicación web progresiva que permite a los usuarios capturar fotos y videos y subirlos. Usamos WebRTC cuando es posible, pero recurrimos a los selectores de archivos HTML5 para dispositivos con menos soporte * tos de Safari *. Si está trabajando específicamente en una aplicación web móvil Android / iOS que usa la cámara nativa para capturar fotos / videos directamente, entonces esta es la mejor solución que he encontrado.

El quid de este problema es que cuando se carga la página, el filees null, pero cuando el usuario abre el cuadro de diálogo y presiona "Cancelar", el filees todavía null, por lo que no "cambió", por lo que no se activa ningún evento de "cambio". Para las computadoras de escritorio, esto no es tan malo porque la mayoría de las interfaces de usuario de escritorio no dependen de saber cuándo se invoca una cancelación, pero las UI móviles que muestran la cámara para capturar una foto / video dependen mucho de saber cuándo se presiona una cancelación.

Originalmente usé el document.body.onfocusevento para detectar cuándo el usuario regresó del selector de archivos, y esto funcionó para la mayoría de los dispositivos, pero iOS 11.3 lo rompió ya que ese evento no se activa.

Concepto

Mi solución a esto es * estremecimiento * para medir el tiempo de la CPU para determinar si la página está actualmente en primer plano o en segundo plano. En los dispositivos móviles, el tiempo de procesamiento se asigna a la aplicación que se encuentra actualmente en primer plano. Cuando una cámara es visible, robará tiempo de CPU y despriorizará al navegador. Todo lo que tenemos que hacer es medir cuánto tiempo de procesamiento se le da a nuestra página, cuando se inicia la cámara, nuestro tiempo disponible se reducirá drásticamente. Cuando se desconecta la cámara (ya sea cancelada o no), nuestro tiempo disponible aumenta de nuevo.

Implementación

Podemos medir el tiempo de la CPU usando setTimeout()para invocar una devolución de llamada en X milisegundos y luego medir cuánto tiempo tomó realmente invocarla. El navegador nunca lo invocará exactamente después de X milisegundos, pero si está razonablemente cerca, entonces debemos estar en primer plano. Si el navegador está muy lejos (más de 10 veces más lento de lo solicitado) entonces debemos estar en segundo plano. Una implementación básica de esto es así:

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);
  });
}

Probé esto en una versión reciente de iOS y Android, abriendo la cámara nativa configurando los atributos en el <input />elemento.

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

En realidad, esto funciona mucho mejor de lo que esperaba. Ejecuta 10 pruebas solicitando que se invoque un temporizador en 25 milisegundos. Luego mide cuánto tiempo realmente tomó invocar, y si el promedio de 10 intentos es menos de 50 milisegundos, asumimos que debemos estar en primer plano y la cámara no está. Si es superior a 50 milisegundos, entonces todavía debemos estar en segundo plano y debemos seguir esperando.

Algunos detalles adicionales

Usé en setTimeout()lugar de setInterval()porque este último puede poner en cola múltiples invocaciones que se ejecutan inmediatamente una después de la otra. Esto podría aumentar drásticamente el ruido en nuestros datos, así que me quedé setTimeout()a pesar de que es un poco más complicado hacerlo.

Estos números en particular funcionaron bien para mí, aunque he visto al menos una instancia en la que el disparo de la cámara se detectó prematuramente. Creo que esto se debe a que la cámara puede tardar en abrirse y el dispositivo puede ejecutar 10 pruebas antes de que se vuelva a poner en segundo plano. Agregar más pruebas o esperar entre 25 y 50 milisegundos antes de iniciar esta función puede ser una solución alternativa.

Escritorio

Desafortunadamente, esto realmente no funciona para los navegadores de escritorio. En teoría, el mismo truco es posible, ya que priorizan la página actual sobre las páginas en segundo plano. Sin embargo, muchos escritorios tienen suficientes recursos para mantener la página funcionando a toda velocidad incluso cuando están en segundo plano, por lo que esta estrategia no funciona en la práctica.

Soluciones alternativas

Una solución alternativa que no mucha gente menciona que exploré fue burlarme de un FileList. Comenzamos con nullen <input />y luego, si el usuario abre la cámara y cancela, regresa null, lo cual no es un cambio y no se activará ningún evento. Una solución sería asignar un archivo ficticio al <input />inicio de la página, por lo tanto, establecer ennull sería un cambio que desencadenaría el evento apropiado.

Desafortunadamente, no hay forma oficial de crear un FileList, y el <input />elemento requiere un FileListen particular y no aceptará ningún otro valor además null. Naturalmente, los FileListobjetos no se pueden construir directamente, debido a algún problema de seguridad antiguo que aparentemente ya no es relevante. La única forma de conseguir uno fuera de un <input />elemento es utilizar un truco que copia y pega datos para simular un evento de portapapeles que puede contener un FileListobjeto (básicamente estás fingiendo arrastrar y soltar un archivo en evento de su sitio web). Esto es posible en Firefox, pero no para iOS Safari, por lo que no era viable para mi caso de uso particular.

Navegadores, por favor ...

No hace falta decir que esto es evidentemente ridículo. El hecho de que a las páginas web no se les notifique que un elemento crítico de la interfaz de usuario ha cambiado es simplemente ridículo. Esto es realmente un error en la especificación, ya que nunca fue diseñado para una interfaz de usuario de captura de medios a pantalla completa, y no activar el evento de "cambio" es técnicamente conforme a las especificaciones.

Sin embargo , ¿pueden los proveedores de navegadores reconocer la realidad de esto? Esto podría resolverse con un nuevo evento "hecho" que se activa incluso cuando no se produce ningún cambio, o simplemente puede activar "cambio" de todos modos. Sí, eso iría en contra de las especificaciones, pero es trivial para mí deducir un evento de cambio en el lado de JavaScript, aunque fundamentalmente imposible inventar mi propio evento "hecho". Incluso mi solución es realmente solo heurística, si no ofrezco garantías sobre el estado del navegador.

Tal como está, esta API es fundamentalmente inutilizable para dispositivos móviles, y creo que un cambio de navegador relativamente simple podría hacer esto infinitamente más fácil para los desarrolladores web * pasos fuera de la caja de jabón *.



7

Cuando selecciona un archivo y hace clic en abrir / cancelar, el inputelemento debería perder el foco, también conocido como blur. Suponiendo que la inicial valuede inputestá vacía, cualquier valor no vacío en su blurcontrolador indicaría un OK, y un valor vacío significaría un Cancelar.

ACTUALIZACIÓN: blurno se activa cuando inputestá oculto. Por lo tanto, no puede usar este truco con cargas basadas en IFRAME, a menos que desee mostrar temporalmente el input.


3
En Firefox 10 beta y Chrome 16, blurno se activa cuando selecciona un archivo. No lo he probado en otros navegadores.
Blaise

1
Eso no funciona: el desenfoque de la entrada se activa inmediatamente después del clic. Chrome 63.0.3239.84 x64 en 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 */
    }
});

6
¿Ha comprobado si elsealguna vez se evalúa su estado de cuenta?
jayarjo

Por lo que recuerdo cuando escribí esta respuesta, si el usuario selecciona un archivo y luego vuelve a seleccionar el archivo (pero luego cancela), se tratará como si no hubiera ningún archivo seleccionado, solo uso alert en la variable selected_file_name para probarlo. de todos modos, no lo he probado más desde entonces.
Rizky

4
Puedo decir, después de algunas pruebas en varios navegadores, que Firefox no activa un evento de cambio al cancelar, ni borra los archivos existentes seleccionados si se vuelven a abrir y se hace clic en cancelar la segunda vez. Extraño, ¿eh?
mix3d

7

La mayoría de estas soluciones no me funcionan.

El problema es que nunca se sabe qué evento se desencadenará, ¿verdad clicko no change? No puede asumir ningún orden, porque probablemente depende de la implementación del navegador.

Al menos en Opera y Chrome (finales de 2015) clickse activa justo antes de 'llenar' la entrada con archivos, por lo que nunca sabrá la duración files.length != 0hasta que se demore clicken activarse después change.

Aquí está el código:

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);
    };
});

Si hace clic en la página ... "Cancelar clic" ... = | (Probado con Chrome)
Eduardo Lucio

5

Solo escuche el evento de clic también.

Siguiendo el ejemplo de Shiboe, aquí hay un ejemplo de 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!');
    }

});

Puedes verlo en acción aquí: http://jsfiddle.net/T3Vwz


1
Probé en Chrome 51, no funciona la primera vez que seleccionas archivos. La solución es agregar un retraso: jsfiddle.net/7jfbmunh
Benoit Blanchon

Tu violín no funcionó para mí - usando Firefox 58.0.2
barrypicker

4

Puede capturar la cancelación si elige el mismo archivo que anteriormente y hace clic en cancelar: en este caso.

Puedes hacerlo así:

<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>

1
Thnkx @loveJs esta publicación realmente me ayudó.
VPK

4
No exactamente, si el usuario selecciona el mismo archivo no es una cancelación = (
Tiago Peres França

13
onchange solo se activa si hubo un cambio. Entonces, si el archivo es el mismo archivo que el anterior, el oyente onchange no se activará.
Shane

1
Puede borrar la selección de entrada (input.value = '') después de detectar el primer cambio, de modo que la segunda vez onchange se activará incluso si el usuario selecciona el mismo archivo nuevamente.
Chris

1
@CodeGems se permite establecer mediante programación el input.value en una cadena vacía. Pero tiene razón en que no se permite establecerlo en ningún otro valor.
Chris

4

La forma más sencilla es comprobar si hay archivos en la memoria temporal. Si desea obtener el evento de cambio cada vez que el usuario hace clic en la entrada del archivo, puede activarlo.

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
    }
});

2

La solución de Shiboe sería buena si funcionara en un webkit móvil, pero no es así. Lo que se me ocurre es agregar un detector de eventos mousemove a algún objeto dom en el momento en que se abre la ventana de entrada del archivo, así:

 $('.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
      };

El evento mousemove se bloquea en la página mientras el cuadro de diálogo del archivo está abierto, y cuando se cierra, se comprueba si hay archivos en la entrada del archivo. En mi caso, quiero un indicador de actividad que bloquee las cosas hasta que se cargue el archivo, por lo que solo quiero eliminar mi indicador al cancelar.

Sin embargo, esto no resuelve para dispositivos móviles, ya que no hay mouse para mover. Mi solución no es perfecta, pero creo que es lo suficientemente buena.

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

Ahora estamos escuchando un toque en la pantalla para hacer la misma verificación de archivos. Estoy bastante seguro de que el dedo del usuario aparecerá en la pantalla con bastante rapidez después de cancelar y descartar este indicador de actividad.

También se podría simplemente agregar el indicador de actividad en el evento de cambio de entrada de archivo, pero en el dispositivo móvil a menudo hay un retraso de unos segundos entre la selección de la imagen y la activación del evento de cambio, por lo que es mucho mejor UX para que el indicador de actividad se muestre en el inicio del proceso.


2

Encontré este atributo, el más simple hasta ahora.

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

Aquí selectedFilehay un input type=file.


Esta es una buena solución para mí, excepto que debería ser> = 1
Bron Thulke

2

Sé que esta es una pregunta muy antigua, pero en caso de que ayude a alguien, descubrí que al usar el evento onmousemove para detectar la cancelación, era necesario probar dos o más eventos de este tipo en un corto espacio de tiempo. Esto se debió a que el navegador (Chrome 65) genera eventos únicos onmousemove cada vez que el cursor se mueve fuera de la ventana de diálogo de selección de archivo y cada vez que se mueve fuera de la ventana principal y vuelve a entrar. Un contador simple de eventos de movimiento del mouse junto con un tiempo de espera de corta duración para restablecer el contador a cero funcionó de maravilla.


1
El primer párrafo es una buena respuesta, aunque podría dividirse en varios para una mejor legibilidad. Pero esas dos líneas "SOAPBOX" lo sitúan en el límite de una No respuesta, ya que las preguntas no pertenecen a las respuestas. ¿Puedes reformular o reformular un poco tu respuesta? Gracias.
Filnor

1

En mi caso, tuve que ocultar el botón de envío mientras los usuarios seleccionaban imágenes.

Esto es lo que se me ocurre:

$(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-fieldes mi selector de archivos. Cuando alguien hace clic en él, desactivo el botón de envío del formulario. El punto es que, cuando se cerró el cuadro de diálogo del archivo, no importa si seleccionan un archivo o cancelan, #image-fieldrecuperaron el enfoque, así que escucho ese evento.

ACTUALIZAR

Descubrí que esto no funciona en safari y poltergeist / phantomjs. Tenga en cuenta esta información si desea implementarla.


1

Combinando las soluciones de Shiboe y alx, tengo el código más confiable:

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 */

Generalmente, el evento mousemove funciona, pero en caso de que el usuario haga un clic y luego:

  • canceló el cuadro de diálogo de apertura de archivo con la tecla de escape (sin mover el mouse), hizo otro clic preciso para abrir el cuadro de diálogo de archivo nuevamente ...
  • cambió el enfoque a cualquier otra aplicación, luego regresó al cuadro de diálogo de apertura del archivo del navegador y lo cerró, luego se abrió nuevamente mediante la tecla enter o la tecla de espacio ...

... no obtendremos el evento mousemove, por lo tanto, no cancelaremos la devolución de llamada. Además, si el usuario cancela el segundo diálogo y hace un movimiento del mouse, obtendremos 2 devoluciones de llamada canceladas. Afortunadamente, el evento jQuery focusIn especial aparece en el documento en ambos casos, lo que nos ayuda a evitar tales situaciones. La única limitación es si uno bloquea el evento focusIn.


Capté el mousemoveevento documentdurante la selección de archivos en el cuadro de diálogo del sistema operativo en Chrome 56.0.2924.87 en Ubuntu 16.10. No hay problema, cuando no mueva el mouse o use solo el teclado para seleccionar el archivo. Tuve que reemplazar mousemovepor keydownpara detectar la cancelación lo antes posible, pero no "demasiado pronto", cuando el cuadro de diálogo aún estaba abierto.
Ferdinand Prantl

1

Veo que mi respuesta estaría bastante desactualizada, pero no por ello menos. Me enfrenté al mismo problema. Entonces esta es mi solución. El código recortado más útil fue el de KGA. Pero no funciona del todo y es un poco complicado. Pero lo simplifiqué.

Además, el principal causante de problemas fue el hecho de que el evento de 'cambio' no se produce instantáneamente después del enfoque, por lo que tenemos que esperar un tiempo.

"#appendfile": en el que el usuario hace clic para agregar un nuevo archivo. Los hrefs obtienen eventos de enfoque.

$("#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);
});

1

Puede detectar esto solo en circunstancias limitadas. Específicamente, en Chrome, si se seleccionó un archivo anteriormente y luego se hace clic en el cuadro de diálogo del archivo y se hace clic en Cancelar, Chrome borra el archivo y activa el evento onChange.

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

En este escenario, puede detectar esto manejando el evento onChange y verificando la propiedad de los archivos .


1

Lo siguiente parece funcionar para mí (en el escritorio, 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;
}

Esto usa angularjs $ q, pero debería poder reemplazarlo con cualquier otro marco de promesa si es necesario.

Probado en IE11, Edge, Chrome, Firefox, pero no parece funcionar en Chrome en una tableta Android ya que no activa el evento Focus.


1

El filecampo -type, frustrantemente, no responde a muchos eventos (el desenfoque sería encantador). Veo a mucha gente sugiriendo changesoluciones orientadas a ellos y son rechazados.

changefunciona, pero tiene un defecto importante (frente a lo que queremos que suceda).

Cuando cargues una página que contenga un campo de archivo, abre el cuadro y presiona cancelar. Nada, frustrantemente, cambia .

Lo que elegí hacer es cargar en un estado cerrado.

  • La siguiente parte del formulario a section#after-imageen mi caso está oculta a la vista. Cuando mi file fieldcambia, se muestra un botón de carga. Luego de una carga exitosa, section#after-imagese muestra.
  • Si el usuario carga, abre el cuadro de diálogo del archivo, luego cancela, nunca ve el botón de carga.
  • Si el usuario elige un archivo, se muestra el botón de carga. Si luego abren el cuadro de diálogo y cancelan, el changeevento se activa mediante esta cancelación, y allí puedo (y lo hago) volver a ocultar mi botón de carga hasta que se seleccione un archivo adecuado.

Tuve la suerte de que este estado cerrado ya fuera el diseño de mi formulario. No es necesario utilizar el mismo estilo, simplemente tener el botón de carga inicialmente oculto y, al cambiarlo, establecer un campo oculto o una variable de javascript en algo que pueda monitorear al enviar.

Intenté cambiar el valor de los archivos [0] antes de interactuar con el campo. Esto no hizo nada con respecto al cambio.

Así que sí, changefunciona, al menos tan bien como vamos a conseguir. El campo de archivo está protegido, por razones obvias, pero para frustración de los desarrolladores bien intencionados.

No se ajusta a mi propósito, pero es posible que pueda onclick, cargar un mensaje de advertencia (no alert(), porque eso detiene el procesamiento de la página) y ocultarlo si se activa el cambio y los archivos [0] son ​​nulos. Si no se activa el cambio, el div permanece en su estado.


0

Hay una forma hackear de hacer esto (agregue devoluciones de llamada o resuelva alguna implementación diferida / promesa en lugar de alert()llamadas):

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);

Cómo funciona: mientras el diálogo de selección de archivos está abierto, el documento no recibe eventos de puntero del mouse. Hay un retraso de 1000 ms para permitir que el cuadro de diálogo aparezca y bloquee la ventana del navegador. Comprobado en Chrome y Firefox (solo Windows).

Pero esta no es una forma confiable de detectar diálogos cancelados, por supuesto. Sin embargo, podría mejorar el comportamiento de la interfaz de usuario.


Un gran inconveniente está en los teléfonos y tabletas, ¡no usan ratones normalmente!
Peter

0

Aquí está mi solución, usando el enfoque de entrada del archivo (sin usar ningún temporizador)

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) {
    // ...
}


Ejecutar su fragmento me daError: { "message": "Uncaught SyntaxError: missing ) after argument list", "filename": "https://stacksnippets.net/js", "lineno": 51, "colno": 1 }
Connexo

0

Esto es hacky en el mejor de los casos, pero aquí hay un ejemplo funcional de mi solución para detectar si un usuario ha subido un archivo o no, y solo permitirle continuar si ha subido un archivo.

Básicamente ocultar el Continue, Save, Proceedo lo que su botón es. Luego, en JavaScript, toma el nombre del archivo. Si el nombre del archivo no tiene un valor, no muestre el Continuebotón. Si tiene un valor, muestre el botón. Esto también funciona si al principio cargan un archivo y luego intentan cargar un archivo diferente y hacen clic en cancelar.

Aquí está el código.

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;
}

Para CSS, mucho de esto es hacer que el sitio web y el botón sean accesibles para todos. Ajusta tu botón a lo que quieras.


0

Bueno, esto no responde exactamente a tu pregunta. Mi suposición es que, tiene un escenario, cuando agrega una entrada de archivo e invoca la selección de archivo, y si el usuario presiona cancelar, simplemente elimina la entrada.

Si este es el caso, entonces: ¿Por qué agregar una entrada de archivo vacía?

Cree el uno sobre la marcha, pero agréguelo a DOM solo cuando esté completo. Así:

    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();

Así que aquí creo <input type = "file" /> sobre la marcha, me vinculo a su evento de cambio y luego invoco inmediatamente el clic. El cambio se activará solo cuando el usuario seleccione un archivo y presione Aceptar; de lo contrario, la entrada no se agregará al DOM, por lo tanto, no se enviará.

Ejemplo de trabajo aquí: https://jsfiddle.net/69g0Lxno/3/


0

// Usa hover en lugar de difuminar

var fileInput = $("#fileInput");

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

    //open
} else {

}

0

Si ya necesita JQuery, esta solución podría hacer el trabajo (este es exactamente el mismo código que realmente necesitaba en mi caso, aunque usar una Promesa es solo para forzar al código a esperar hasta que se resuelva la selección del archivo):

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();

});


0

Hay varias soluciones propuestas en este hilo y esta dificultad para detectar cuando el usuario hace clic en el botón "Cancelar" en el cuadro de selección de archivos es un problema que afecta a muchas personas.

El hecho es que no existe una forma 100% confiable de detectar si el usuario ha hecho clic en el botón "Cancelar" en el cuadro de selección de archivos. Pero hay formas de detectar de manera confiable si el usuario ha agregado un archivo al archivo de entrada. ¡Así que esta es la estrategia básica de esta respuesta!

Decidí agregar esta respuesta porque aparentemente las otras respuestas no funcionan en la mayoría de los navegadores ni están garantizadas en los dispositivos móviles.

Brevemente, el código se basa en 3 puntos:

  1. El archivo de entrada se crea inicialmente dinámicamente en "memoria" en js (no lo agregamos al "HTML" en este momento);
  2. Después de agregar el archivo, el archivo de entrada se agrega al HTML; de lo contrario, no ocurre nada;
  3. La eliminación del archivo se realiza eliminando el archivo de entrada del HTML mediante un evento específico, lo que significa que la "edición" / "modificación" del archivo se realiza eliminando el archivo de entrada anterior y creando uno nuevo.

Para una mejor comprensión, mire el código a continuación y también las notas.

[...]
<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);

}

¡Gracias! = D


0

Logramos en angular como abajo.

    1. enlazar evento de clic en el archivo de tipo de entrada.
      1. Adjunte el evento de enfoque con la ventana y agregue la condición si uploadPanel es verdadero, luego muestre la consola.
      2. al hacer clic en el archivo de tipo de entrada, el valor booleano de uploadPanel es verdadero. y aparece el cuadro de diálogo.
      3. cuando cancele O haga clic en el botón Esc y luego aparecerá el cuadro de diálogo dispensario y la consola.

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();
    }
  }

0

Simplemente agregue el oyente 'cambiar' en su entrada cuyo tipo es archivo. es decir

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

He terminado de usar jQuery y, obviamente, cualquiera puede usar valina JS (según el requisito).

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

            if (this.files.length) {

            alert('file choosen');

            } else {

            alert('file NOT choosen');

            }

    });

.change()no se llama de forma fiable si el usuario hace clic en "cancelar" en el selector de archivos.
Ben Wheeler

@benWheeler, lo he comprobado tanto en Chrome como en Firefox y funcionó, pero no lo probé en otros navegadores.
Naveed Ali

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>

Las respuestas de solo código se consideran de baja calidad: asegúrese de proporcionar una explicación de lo que hace su código y cómo resuelve el problema. Ayudará tanto al autor de la pregunta como a los futuros lectores si puede agregar más información en su publicación. Ver Explicación de respuestas completamente basadas en código
Calos

0

Solución para la selección de archivos con entrada oculta

Nota: este código no detecta la cancelación, ofrece una forma de eludir la necesidad de detectarlo en un caso común en el que las personas intentan detectarlo.

Llegué aquí mientras buscaba una solución para la carga de archivos usando una entrada oculta, creo que esta es la razón más común para buscar una forma de detectar la cancelación de la entrada del archivo (abrir diálogo de archivo -> si se seleccionó un archivo, ejecute algunos código, de lo contrario no haga nada), aquí está mi solución:

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'));
   });
}

Ejemplo de uso:

Tenga en cuenta que si no se seleccionó ningún archivo, la primera línea volverá solo una vez que selectFile()se vuelva a llamar (o si llamó fileSelectorResolve()desde otro lugar).

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

Otro ejemplo:

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

   // ... make an ajax call here to upload the file ...
}

En Chrome 83.0, no recibo ninguna devolución de llamada para el evento de entrada cuando el usuario cancela, y tampoco en el segundo intento
Soylent Graham

1
@SoylentGraham Sí, debido a que este código no detecta la cancelación, ofrece una forma de eludir la necesidad de detectarlo en un caso común en el que las personas intentan detectarlo. Como no lo tenía claro, agrego este comentario a mi respuesta.
papa

Vaya, creo que leí mal tu respuesta como "te dice que el usuario canceló en el segundo intento"
Soylent Graham

-1

Puede hacer un oyente de cambios de jquery en el campo de entrada y detectar que el usuario cancela o cierra la ventana de carga por el valor del campo.

Aquí hay un ejemplo:

//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
    }
});

No tengo idea de por qué se rechaza esto, esto es lo que estaba buscando. ¡Gracias! 🙂
user1274820
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.