Respuestas:
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
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
.
mousemove
caso de window.document
, además de escuchar focus
el window
parece 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.
No puedes.
El resultado del cuadro de diálogo del archivo no se expone al navegador.
change
se envía ningún evento.
change
se envía ningún evento para ese archivo .
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 file
es null
, pero cuando el usuario abre el cuadro de diálogo y presiona "Cancelar", el file
es 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.onfocus
evento 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.
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.
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.
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.
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.
Una solución alternativa que no mucha gente menciona que exploré fue burlarme de un FileList
. Comenzamos con null
en <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 FileList
en particular y no aceptará ningún otro valor además null
. Naturalmente, los FileList
objetos 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 FileList
objeto (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.
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 *.
Cuando selecciona un archivo y hace clic en abrir / cancelar, el input
elemento debería perder el foco, también conocido como blur
. Suponiendo que la inicial value
de input
está vacía, cualquier valor no vacío en su blur
controlador indicaría un OK, y un valor vacío significaría un Cancelar.
ACTUALIZACIÓN: blur
no se activa cuando input
está oculto. Por lo tanto, no puede usar este truco con cargas basadas en IFRAME, a menos que desee mostrar temporalmente el input
.
blur
no se activa cuando selecciona un archivo. No lo he probado en otros navegadores.
/* 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 */
}
});
else
alguna vez se evalúa su estado de cuenta?
La mayoría de estas soluciones no me funcionan.
El problema es que nunca se sabe qué evento se desencadenará, ¿verdad click
o 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) click
se activa justo antes de 'llenar' la entrada con archivos, por lo que nunca sabrá la duración files.length != 0
hasta que se demore click
en 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);
};
});
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
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>
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
}
});
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.
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í selectedFile
hay un input type=file
.
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.
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-field
es 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-field
recuperaron 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.
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:
... 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.
mousemove
evento document
durante 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 mousemove
por keydown
para detectar la cancelación lo antes posible, pero no "demasiado pronto", cuando el cuadro de diálogo aún estaba abierto.
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);
});
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 .
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.
El file
campo -type, frustrantemente, no responde a muchos eventos (el desenfoque sería encantador). Veo a mucha gente sugiriendo change
soluciones orientadas a ellos y son rechazados.
change
funciona, 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.
section#after-image
en mi caso está oculta a la vista. Cuando mi file field
cambia, se muestra un botón de carga. Luego de una carga exitosa, section#after-image
se muestra.change
evento 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í, change
funciona, 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.
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.
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) {
// ...
}
Error: { "message": "Uncaught SyntaxError: missing ) after argument list", "filename": "https://stacksnippets.net/js", "lineno": 51, "colno": 1 }
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
, Proceed
o 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 Continue
botó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.
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/
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();
});
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:
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
Logramos en angular como abajo.
<input type="file" formControlName="FileUpload" click)="handleFileInput($event.target.files)" />
/>
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();
}
}
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.
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>
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'));
});
}
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 ...
}
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
}
});
e.target.files