Como detectar quando cancelar é clicado na entrada do arquivo?

112

Como posso detectar quando o usuário cancela uma entrada de arquivo usando uma entrada de arquivo html?

onChange me permite detectar quando eles escolhem um arquivo, mas eu também gostaria de saber quando eles cancelam (feche a caixa de diálogo de escolha de arquivo sem selecionar nada).

Ppp
fonte
Você também pode detectar se alguém escolheu o arquivo e, em seguida, tentar escolher enviar um, mas cancelar. Você ficará vazioe.target.files
jcubic

Respostas:

45

Embora não seja uma solução direta, e também ruim por funcionar apenas (pelo que testei) com onfocus (exigindo um bloqueio de evento bastante limitador), você pode obtê-lo com o seguinte:

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

O que é bom nisso, é que você pode anexar / desanexar no tempo com o evento de arquivo, e também parece funcionar bem com entradas ocultas (uma vantagem definitiva se você estiver usando uma solução alternativa visual para a entrada padrão de baixa qualidade type = ' Arquivo'). Depois disso, você só precisa descobrir se o valor de entrada mudou.

Um exemplo:

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

Veja-o em ação: http://jsfiddle.net/Shiboe/yuK3r/6/

Infelizmente, parece funcionar apenas em navegadores webkit. Talvez outra pessoa possa descobrir a solução do firefox / IE

Shiboe
fonte
Eu não queria que o evento de foco disparasse sempre, então eu queria usar addEventListener("focus",fn,{once: true}). No entanto, não consegui disparar usando document.body.addEventListener.. Não sei por quê. Em vez disso, obtive o mesmo resultado com window.addEventListener.
WoodenKitty
3
Isso não funciona no Firefox e no Edge para mim. No entanto, para ouvir o mousemoveevento no window.document além para ouvir focussobre windowparece funcionar em todos os lugares (pelo menos em navegadores modernos, eu não me importo com destroços passado-década). Basicamente, qualquer evento de interação pode ser usado para esta tarefa que é bloqueado por uma caixa de diálogo de abertura de arquivo aberta.
John Weisz,
@WoodenKitty como você implementou isso usando o addEventListener. Estou tentando fazer isso no angular e no documento. Ninguém está trabalhando para mim também
Terrance Jackson
isso não funciona em campos de entrada ocultos.
Sebastian
20

Você não pode.

O resultado da caixa de diálogo do arquivo não é exposto ao navegador.

Oded
fonte
É possível verificar se o seletor de arquivos está aberto ou não?
Ppp de
1
@Ppp - Não. O navegador não informa isso.
Oded
6
"O resultado da caixa de diálogo do arquivo não é exposto ao navegador." Isso não é verdade se a caixa de diálogo for fechada ao selecionar um arquivo.
Trevor
3
A mudança acionada usando a caixa de diálogo de arquivo é exposta ao navegador. Veja, se não houver nenhum arquivo selecionado, e nenhum arquivo for selecionado posteriormente, então nada mudou, portanto, nenhum changeevento é despachado.
John Weisz de
1
Além disso, se um arquivo já estiver selecionado e você selecionar o mesmo arquivo novamente, nenhum changeevento será despachado para isso.
Patrick Roberts
16

Então, vou jogar meu chapéu nessa questão, já que encontrei uma nova solução. Eu tenho um Progressive Web App que permite aos usuários capturar fotos e vídeos e carregá-los. Usamos WebRTC quando possível, mas recorremos a seletores de arquivo HTML5 para dispositivos com menos suporte * tosse Safari tosse *. Se você estiver trabalhando especificamente em um aplicativo da web móvel Android / iOS que usa a câmera nativa para capturar fotos / vídeos diretamente, esta é a melhor solução que encontrei.

O ponto crucial desse problema é que, quando a página carrega, o fileé null, mas quando o usuário abre a caixa de diálogo e pressiona "Cancelar", o fileé null, portanto, não "mudou", portanto, nenhum evento de "alteração" é acionado. Para desktops, isso não é tão ruim porque a maioria das IUs de desktops não dependem de saber quando um cancelamento é invocado, mas as IUs móveis que ativam a câmera para capturar uma foto / vídeo dependem muito de saber quando um cancelamento é pressionado.

Eu usei originalmente o document.body.onfocusevento para detectar quando o usuário retornou do seletor de arquivos, e isso funcionou para a maioria dos dispositivos, mas o iOS 11.3 o quebrou porque o evento não é acionado.

Conceito

Minha solução para isso é * tremor * para medir o tempo da CPU para determinar se a página está atualmente em primeiro ou segundo plano. Em dispositivos móveis, o tempo de processamento é atribuído ao aplicativo atualmente em primeiro plano. Quando uma câmera está visível, ela rouba o tempo da CPU e diminui a prioridade do navegador. Tudo o que precisamos fazer é medir quanto tempo de processamento é dado à nossa página, quando a câmera for iniciada, nosso tempo disponível cairá drasticamente. Quando a câmera é dispensada (cancelada ou não), nosso tempo disponível aumenta.

Implementação

Podemos medir o tempo da CPU usando setTimeout()para invocar um retorno de chamada em X milissegundos e, em seguida, medir quanto tempo levou para invocá-lo de fato. O navegador nunca o invocará exatamente após X milissegundos, mas se estiver razoavelmente perto, devemos estar em primeiro plano. Se o navegador estiver muito longe (mais de 10 vezes mais lento do que o solicitado), devemos estar em segundo plano. Uma implementação básica disso é assim:

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

Eu testei isso em uma versão recente do iOS e Android, trazendo a câmera nativa definindo os atributos no <input />elemento.

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

Na verdade, isso funciona muito melhor do que eu esperava. Ele executa 10 testes solicitando que um cronômetro seja chamado em 25 milissegundos. Em seguida, mede quanto tempo realmente levou para invocar e, se a média de 10 tentativas for inferior a 50 milissegundos, presumimos que devemos estar em primeiro plano e a câmera sumiu. Se for maior que 50 milissegundos, ainda devemos estar em segundo plano e continuar aguardando.

Alguns detalhes adicionais

Usei em setTimeout()vez de setInterval()porque o último pode enfileirar várias invocações que são executadas imediatamente uma após a outra. Isso poderia aumentar drasticamente o ruído em nossos dados, então continuei setTimeout(), embora seja um pouco mais complicado de fazer.

Esses números específicos funcionaram bem para mim, embora eu tenha visto pelo menos uma vez em que o descarte da câmera foi detectado prematuramente. Acredito que isso seja porque a câmera pode demorar para abrir e o dispositivo pode executar 10 tentativas antes de realmente entrar em segundo plano. Adicionar mais tentativas ou esperar cerca de 25-50 milissegundos antes de iniciar esta função pode ser uma solução alternativa para isso.

Área de Trabalho

Infelizmente, isso realmente não funciona para navegadores de desktop. Em teoria, o mesmo truque é possível, pois eles priorizam a página atual sobre as páginas em segundo plano. No entanto, muitos desktops têm recursos suficientes para manter a página em execução em velocidade total, mesmo quando em segundo plano, então essa estratégia não funciona na prática.

Soluções alternativas

Uma solução alternativa que poucas pessoas mencionam e que explorei foi zombar de a FileList. Começamos com nullno <input />e, em seguida, se o usuário abrir a câmera e cancelar, eles voltam null, o que não é uma alteração e nenhum evento será acionado. Uma solução seria atribuir um arquivo fictício ao <input />início da página, portanto, definir como nullseria uma alteração que acionaria o evento apropriado.

Infelizmente, não existe uma maneira oficial de criar um FileList, e o <input />elemento requer um FileListem particular e não aceitará nenhum outro valor além disso null. Naturalmente, os FileListobjetos não podem ser construídos diretamente, devido a algum velho problema de segurança que aparentemente não é mais relevante. A única maneira de obter alguém de fora de um <input />elemento é utilizar um hack que copia e cola dados para simular um evento da área de transferência que pode conter um FileListobjeto (você está basicamente fingindo arrastar e soltar um arquivo evento do seu site). Isso é possível no Firefox, mas não para iOS Safari, portanto, não era viável para meu caso de uso específico.

Navegadores, por favor ...

Nem é preciso dizer que isso é ridículo. O fato de que as páginas da web não recebem nenhuma notificação de que um elemento crítico da IU foi alterado é simplesmente ridículo. Este é realmente um bug na especificação, já que nunca foi planejado para uma IU de captura de mídia em tela inteira, e não acionar o evento "alterar" é tecnicamente uma especificação.

No entanto , os fornecedores de navegadores podem reconhecer a realidade disso? Isso pode ser resolvido com um novo evento "concluído", que é acionado mesmo quando nenhuma alteração ocorre, ou você pode simplesmente acionar "alteração" de qualquer maneira. Sim, isso seria contra a especificação, mas é trivial para mim desduplicar um evento de alteração no lado do JavaScript, mas fundamentalmente impossível inventar meu próprio evento "concluído". Mesmo minha solução é realmente apenas heurística, se não oferecer garantias sobre o estado do navegador.

Do jeito que está, essa API é fundamentalmente inutilizável para dispositivos móveis e acho que uma mudança relativamente simples no navegador poderia tornar isso infinitamente mais fácil para os desenvolvedores da web * sair da caixa de sabão *.

Douglas Parker
fonte
7

Quando você seleciona um arquivo e clica em abrir / cancelar, o inputelemento deve perder o foco, também conhecido como blur. Assumindo que a inicial valuede inputestá vazia, qualquer valor não vazio em seu blurmanipulador indicaria um OK e um valor vazio significaria um Cancelar.

ATUALIZAÇÃO: O blurnão é acionado quando o inputestá oculto. Portanto, não é possível usar esse truque com uploads baseados em IFRAME, a menos que você queira exibir temporariamente o input.

zvolkov
fonte
3
No Firefox 10 beta e Chrome 16, blurnão é acionado quando você seleciona um arquivo. Não tentei em outros navegadores.
Blaise
1
Isso não funciona - o desfoque na entrada é acionado imediatamente após o clique. Chrome 63.0.3239.84 x64 no 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
fonte
6
Você verificou se sua elsedeclaração foi avaliada?
Jayarjo
Pelo que me lembro quando escrevi esta resposta, se o usuário selecionar um arquivo e, em seguida, selecionar novamente o arquivo (mas depois cancelar), ele será tratado como nenhum arquivo selecionado, eu só uso o alerta na variável selected_file_name para testá-lo. de qualquer maneira, não testei mais desde então.
Rizky
4
Posso dizer, depois de alguns testes de navegador cruzado, que o Firefox não dispara um evento de alteração no cancelamento, nem limpa os arquivos existentes selecionados se forem reabertos e o cancelamento clicado pela segunda vez. Estranho, hein?
mix3d
7

A maioria dessas soluções não funciona para mim.

O problema é que você nunca sabe qual evento será disparado punho, é clickou é change? Você não pode assumir nenhuma ordem, porque provavelmente depende da implementação do navegador.

Pelo menos no Opera e no Chrome (final de 2015) clické acionado um pouco antes de 'preencher' a entrada com arquivos, então você nunca saberá a duração files.length != 0até o atraso clickpara ser acionado depois change.

Aqui está o 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);
    };
});
superdario128
fonte
Se você clicar na página ... "Cancelar clicado" ... = | (Testado com Chrome)
Eduardo Lucio
5

Apenas ouça o evento de clique também.

Seguindo o exemplo de Shiboe, aqui está um exemplo 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!');
    }

});

Você pode vê-lo em ação aqui: http://jsfiddle.net/T3Vwz

AEQ
fonte
1
Testei no Chrome 51, ele não funciona na primeira vez que você seleciona arquivos. A solução é adicionar um atraso: jsfiddle.net/7jfbmunh
Benoit Blanchon
Seu violino não funcionou para mim - usando o Firefox 58.0.2
barrypicker
4

Você pode pegar o cancelamento se escolher o mesmo arquivo anterior e clicar em cancelar: neste caso.

Você pode fazer assim:

<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
fonte
1
Thnkx @loveJs este post realmente me ajudou.
VPK de
4
Não exatamente, se o usuário seleciona o mesmo arquivo não é um cancel = (
Tiago Peres França
13
onchange só dispara se houver uma mudança. Portanto, se o arquivo for o mesmo que o anterior, o ouvinte do onchange não será acionado.
Shane
1
Você pode limpar a seleção de entrada (input.value = '') depois de detectar a primeira alteração, para que o onchange da segunda vez seja acionado mesmo se o usuário selecionar o mesmo arquivo novamente.
Chris
1
@CodeGems é permitido definir programaticamente o input.value como uma string vazia. Mas você está certo ao dizer que não é permitido definir qualquer outro valor.
Chris
4

A maneira mais fácil é verificar se há algum arquivo na memória temporária. Se você deseja obter o evento de mudança toda vez que o usuário clica na entrada do arquivo, você pode acioná-lo.

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

A solução de Shiboe seria boa se funcionasse no webkit móvel, mas não funciona. O que posso fazer é adicionar um ouvinte de evento mousemove a algum objeto dom no momento em que a janela de entrada do arquivo é aberta, assim:

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

O evento mousemove é bloqueado na página enquanto a caixa de diálogo do arquivo está aberta e, quando fechada, verifica se há algum arquivo na entrada do arquivo. No meu caso, quero um indicador de atividade bloqueando as coisas até que o arquivo seja carregado, então só quero remover meu indicador no cancelamento.

No entanto, isso não resolve para o celular, já que não há mouse para mover. Minha solução não é perfeita, mas acho que é boa o suficiente.

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

Agora estamos ouvindo um toque na tela para fazer a mesma verificação de arquivos. Tenho certeza de que o dedo do usuário será colocado na tela rapidamente após cancelar e descartar este indicador de atividade.

Pode-se também adicionar o indicador de atividade no evento de alteração de entrada de arquivo, mas no celular geralmente há alguns segundos de atraso entre a seleção da imagem e o disparo do evento de alteração, então é muito melhor UX para o indicador de atividade a ser exibido no início do processo.

ransomweaver
fonte
2

Achei esse atributo, é mais simples ainda.

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

Aqui selectedFileestá um input type=file.

Israel
fonte
Esta é uma boa solução para mim, exceto que deve ser> = 1
Bron Thulke
2

Sei que esta é uma pergunta muito antiga, mas caso ajude alguém, descobri ao usar o evento onmousemove para detectar o cancelamento, que era necessário testar dois ou mais eventos em um curto espaço de tempo. Isso ocorreu porque eventos onmousemove únicos são gerados pelo navegador (Chrome 65) cada vez que o cursor é movido para fora da janela de diálogo de arquivo de seleção e cada vez que é movido para fora da janela principal e de volta para dentro. Um contador simples de eventos de movimento do mouse juntamente com um tempo limite de curta duração para zerar o contador, funcionou muito bem.

Steve Brooker
fonte
1
O primeiro parágrafo é uma boa resposta, embora possa ser dividido em vários para melhor legibilidade. Mas essas duas linhas "SOAPBOX" tornam-no no limite de uma Não Resposta, pois as perguntas não pertencem às respostas. Você pode reformular / reformular sua resposta um pouco? Obrigado.
Filnor
1

No meu caso, tive que ocultar o botão enviar enquanto os usuários selecionavam as imagens.

Isso é o que eu aponto:

$(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é meu seletor de arquivo. Quando alguém clica nele, eu desabilito o botão de envio do formulário. O que quero dizer é que, quando a caixa de diálogo de arquivo é fechada - não importa se eles selecionam um arquivo ou cancelam - #image-fieldrecebe o foco de volta, então ouço esse evento.

ATUALIZAR

Descobri que isso não funciona em safari e poltergeist / phantomjs. Leve essa informação em consideração se quiser implementá-la.

bonyiii
fonte
1

Combinando as soluções Shiboe's e alx's, tenho o código mais confiável:

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

Geralmente, o evento mousemove resolve, mas caso o usuário clique e:

  • cancelou a caixa de diálogo de abertura de arquivo com a tecla Escape (sem mover o mouse), fez outro clique preciso para abrir a caixa de diálogo de arquivo novamente ...
  • mudou o foco para qualquer outro aplicativo, depois voltou para a caixa de diálogo de abertura de arquivo do navegador e fechou-a, depois abriu novamente com a tecla Enter ou a tecla de espaço ...

... não teremos o evento mousemove, portanto, nenhum retorno de chamada cancelado. Além disso, se o usuário cancelar a segunda caixa de diálogo e mover o mouse, obteremos 2 callbacks de cancelamento. Felizmente, o evento especial do jQuery focusIn surge no documento em ambos os casos, ajudando-nos a evitar tais situações. A única limitação é bloquear o evento focusIn também.

KGA
fonte
Eu percebi o mousemoveevento documentdurante a seleção de arquivos na caixa de diálogo do sistema operacional no Chrome 56.0.2924.87 no Ubuntu 16.10. Sem problemas, quando não movo o mouse, ou use apenas o teclado para selecionar o arquivo. Tive que substituir mousemovepor keydownpara detectar o cancelamento o mais cedo possível, mas não "muito cedo", quando a caixa de diálogo ainda estava aberta.
Ferdinand Prantl
1

Vejo que minha resposta seria bastante desatualizada, mas nunca menos. Eu enfrentei o mesmo problema. Então aqui está minha solução. O código mais útil cortado foi o do KGA. Mas não está funcionando totalmente e é um pouco complicado. Mas eu simplifiquei.

Além disso, o principal causador de problemas era o fato de que o evento de 'mudança' não ocorre instantaneamente após o foco, então temos que esperar algum tempo.

"#appendfile" - no qual o usuário clica para anexar um novo arquivo. Hrefs obtém eventos de foco.

$("#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
fonte
1

Você pode detectar isso apenas em circunstâncias limitadas. Especificamente, no Chrome, se um arquivo foi selecionado anteriormente e a caixa de diálogo do arquivo é clicada e cancelada, o Chrome limpa o arquivo e dispara o evento onChange.

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

Nesse cenário, você pode detectar isso manipulando o evento onChange e verificando a propriedade de arquivos .

Kunal
fonte
1

O seguinte parece funcionar para mim (no desktop, 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;
}

Isso usa angularjs $ q, mas você deve ser capaz de substituí-lo por qualquer outra estrutura de promessa, se necessário.

Testado no IE11, Edge, Chrome, Firefox, mas parece não funcionar no Chrome em um tablet Android, pois não dispara o evento Focus.

Peter
fonte
1

O filecampo -type, frustrantemente, não responde a muitos eventos (desfoque seria ótimo). Eu vejo muitas pessoas sugerindo changesoluções orientadas e sendo rejeitadas.

changefunciona, mas tem uma grande falha (vs o que queremos que aconteça).

Ao carregar recentemente uma página que contém um campo de arquivo, abra a caixa e pressione cancelar. Nada muda , frustrantemente .

O que escolhi fazer é carregar em um estado fechado.

  • A próxima parte do formulário a, section#after-imageno meu caso, está oculta. Quando minhas file fieldalterações, um botão de upload é mostrado. Após o upload bem-sucedido, section#after-imageé mostrado.
  • Se o usuário carregar, abrir a caixa de diálogo de arquivo e cancelar, ele nunca verá o botão de upload.
  • Se o usuário escolher um arquivo, o botão de upload será mostrado. Se eles abrirem a caixa de diálogo e cancelar, o changeevento é acionado por este cancelamento, e aí eu posso (e faço) ocultar novamente meu botão de upload até que um arquivo adequado seja selecionado.

Tive a sorte de que este estado fechado já fosse o desenho do meu formulário. Você não precisa usar o mesmo estilo, simplesmente mantendo o botão de upload inicialmente oculto e, após a alteração, definindo um campo oculto ou variável javascript para algo que você possa monitorar no envio.

Tentei alterar o valor de files [0] antes de interagir com o campo. Isso não fez nada em relação ao onchange.

Então, sim, changefunciona, pelo menos tão bom quanto vamos conseguir. O campo de arquivos está protegido, por razões óbvias, mas para a frustração de desenvolvedores bem-intencionados.

Não é adequado ao meu propósito, mas você pode ser capaz de onclickcarregar um prompt de aviso (não um alert(), porque isso interrompe o processamento da página) e ocultá-lo se a alteração for acionada e o arquivo [0] for nulo. Se a mudança não for acionada, o div permanecerá em seu estado.

Joe normal
fonte
0

Existe uma maneira hackeada de fazer isso (adicionar callbacks ou resolver alguma implementação adiada / prometida em vez de alert()chamadas):

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

Como funciona: enquanto a caixa de diálogo de seleção de arquivo está aberta, o documento não recebe eventos do ponteiro do mouse. Há um atraso de 1000 ms para permitir que o diálogo realmente apareça e bloqueie a janela do navegador. Verificado no Chrome e Firefox (somente Windows).

Mas essa não é uma maneira confiável de detectar diálogos cancelados, é claro. Porém, pode melhorar algum comportamento da IU para você.

Alx
fonte
Uma grande desvantagem está nos telefones e tablets, eles não usam o mouse normalmente!
Peter,
0

Aqui está minha solução, usando o foco de entrada de arquivo (sem usar nenhum 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) {
    // ...
}

Yovav
fonte
Executar seu snippet me dáError: { "message": "Uncaught SyntaxError: missing ) after argument list", "filename": "https://stacksnippets.net/js", "lineno": 51, "colno": 1 }
connexo
0

Isso é hacky na melhor das hipóteses, mas aqui está um exemplo prático da minha solução para detectar se um usuário carregou ou não um arquivo, e apenas permitindo que ele prossiga se ele tiver carregado um arquivo.

Basicamente esconder o Continue, Save, Proceedou qualquer que seja o seu botão é. Então, no JavaScript, você pega o nome do arquivo. Se o nome do arquivo não tiver um valor, não mostre o Continuebotão. Se tiver um valor, mostre o botão. Isso também funciona se eles primeiro fizerem upload de um arquivo e, em seguida, tentarem fazer upload de um arquivo diferente e clicarem em cancelar.

Aqui está o 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 o CSS, muito disso é tornar o site e o botão acessíveis para todos. Defina o estilo do seu botão como quiser.

maxshuty
fonte
0

Bem, isso não responde exatamente à sua pergunta. Minha suposição é que, você tem um cenário, quando adiciona uma entrada de arquivo e invoca a seleção de arquivo e, se o usuário clicar em cancelar, você apenas remove a entrada.

Se for esse o caso, então: Por que adicionar entrada de arquivo vazio?

Crie um na hora, mas adicione-o ao DOM apenas quando estiver preenchido. Assim:

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

Portanto, aqui eu crio <input type = "file" /> dinamicamente, vinculo a seu evento de alteração e invoco imediatamente o clique. A alteração será acionada apenas quando o usuário selecionar um arquivo e clicar em Ok, caso contrário, a entrada não será adicionada ao DOM e, portanto, não será enviada.

Exemplo de trabalho aqui: https://jsfiddle.net/69g0Lxno/3/

Alexey
fonte
0

// Use pairar em vez de desfocar

var fileInput = $("#fileInput");

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

    //open
} else {

}
jsil
fonte
0

Se você já requer JQuery, esta solução pode fazer o trabalho (este é exatamente o mesmo código que eu realmente precisava no meu caso, embora usar uma promessa seja apenas para forçar o código a esperar até que a seleção do arquivo seja resolvida):

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

Existem várias soluções propostas neste tópico e esta dificuldade em detectar quando o usuário clica no botão "Cancelar" na caixa de seleção de arquivos é um problema que afeta muitas pessoas.

O fato é que não existe uma maneira 100% confiável de detectar se o usuário clicou no botão "Cancelar" na caixa de seleção de arquivos. Mas existem maneiras de detectar com segurança se o usuário adicionou um arquivo ao arquivo de entrada. Portanto, esta é a estratégia básica desta resposta!

Decidi adicionar esta resposta porque aparentemente as outras respostas não funcionam na maioria dos navegadores ou garantidas em dispositivos móveis.

Resumidamente, o código é baseado em 3 pontos:

  1. O arquivo de entrada é inicialmente criado dinamicamente na "memória" em js (não o adicionamos ao "HTML" neste momento);
  2. Depois de adicionar o arquivo, o arquivo de entrada é adicionado ao HTML, caso contrário, nada ocorre;
  3. A remoção do arquivo é feita removendo o arquivo de entrada do HTML por um evento específico, o que significa que a "edição" / "modificação" do arquivo é feita removendo o arquivo de entrada antigo e criando um novo.

Para uma melhor compreensão, veja o código abaixo e também as 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);

}

Obrigado! = D

Eduardo lucio
fonte
0

Conseguimos em angular como abaixo.

    1. evento de clique de ligação no arquivo de tipo de entrada.
      1. Anexe o evento de foco à janela e adicione a condição se uploadPanel for verdadeiro, então mostre o console.
      2. ao clicar no arquivo de tipo de entrada, o valor booleano uploadPanel é verdadeiro. e uma caixa de diálogo aparece.
      3. quando cancelar OU o botão Esc, clique em seguida, caixa de diálogo dispensário e console aparecem.

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

Basta adicionar o ouvinte 'alterar' em sua entrada cujo tipo é arquivo. ie

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

Eu usei jQuery e obviamente qualquer um pode usar valina JS (conforme o requisito).

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

            if (this.files.length) {

            alert('file choosen');

            } else {

            alert('file NOT choosen');

            }

    });
Naveed Ali
fonte
.change()não é chamado de forma confiável se o usuário clicar em "cancelar" no seletor de arquivos.
Ben Wheeler
@benWheeler, eu verifiquei no Chrome e no Firefox e funcionou, mas não testei em outros 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>
Emmanuel Onah
fonte
Respostas somente de código são consideradas de baixa qualidade: certifique-se de fornecer uma explicação sobre o que seu código faz e como ele resolve o problema. Ajudará tanto o autor da pergunta quanto os futuros leitores se você puder adicionar mais informações em sua postagem. Consulte Explicando respostas inteiramente baseadas em código
Calos
0

Solução para seleção de arquivo com entrada oculta

Nota: este código não detecta o cancelamento, ele oferece uma maneira de contornar a necessidade de detectá-lo em um caso comum em que as pessoas tentam detectá-lo.

Eu cheguei aqui enquanto procurava uma solução para uploads de arquivos usando uma entrada oculta, acredito que este seja o motivo mais comum para procurar uma maneira de detectar o cancelamento da entrada de arquivo (caixa de diálogo abrir arquivo -> se um arquivo foi selecionado, execute alguns código, caso contrário, não faça nada), aqui está minha solução:

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

Exemplo de uso:

Observe que se nenhum arquivo foi selecionado, a primeira linha retornará apenas uma vez selectFile() for chamada novamente (ou se você ligou fileSelectorResolve()de outro lugar).

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

Outro exemplo:

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

   // ... make an ajax call here to upload the file ...
}
batata
fonte
No Chrome 83.0, não recebo retorno de chamada para o evento de entrada quando o usuário cancela, e não na 2ª tentativa
Soylent Graham
1
@SoylentGraham Sim, porque este código não detecta o cancelamento, ele oferece uma maneira de contornar a necessidade de detectá-lo em um caso comum em que as pessoas tentam detectá-lo. Visto que não fui claro sobre isso, estou adicionando este comentário à minha resposta.
batata
Foi mal, eu acho, eu interpretei mal sua resposta como "diz que o usuário cancelou na 2ª tentativa"
Soylent Graham
-1

Você pode fazer um ouvinte de mudança jquery no campo de entrada e detectar que o usuário cancelou ou fechou a janela de upload pelo valor do campo.

aqui está um exemplo:

//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
fonte
Não tenho ideia de por que isso foi rejeitado - isso é o que eu estava procurando. Obrigado! 🙂
user1274820