Jak wykryć kliknięcie przycisku Anuluj na wejściu pliku?


112

Jak mogę wykryć, że użytkownik anuluje dane wejściowe pliku za pomocą danych wejściowych w formacie html?

onChange pozwala mi wykryć, kiedy wybierają plik, ale chciałbym również wiedzieć, kiedy anulują (zamknij okno dialogowe wyboru pliku bez wybierania czegokolwiek).


Możesz także wykryć, czy ktoś wybierze plik, a następnie spróbować go wysłać, ale anuluj, otrzymasz pustye.target.files
jcubic

Odpowiedzi:


45

Chociaż nie jest to bezpośrednie rozwiązanie, a także złe, ponieważ (o ile testowałem) działa tylko z ogniskiem (wymagającym dość ograniczającego blokowania zdarzeń), możesz to osiągnąć, wykonując następujące czynności:

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

Fajne w tym jest to, że możesz dołączyć / odłączyć go na czas ze zdarzeniem pliku, a także wydaje się, że działa dobrze z ukrytymi danymi wejściowymi (zdecydowana korzyść, jeśli używasz wizualnego obejścia dla kiepskiego domyślnego typu wejścia = '' plik'). Następnie wystarczy dowiedzieć się, czy wartość wejściowa uległa zmianie.

Przykład:

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

Zobacz to w akcji: http://jsfiddle.net/Shiboe/yuK3r/6/

Niestety, wygląda na to, że działa tylko w przeglądarkach webkit. Może ktoś inny może wymyślić rozwiązanie Firefox / IE


Nie chciałem, aby zdarzenie ostrości było uruchamiane za każdym razem, więc chciałem użyć addEventListener("focus",fn,{once: true}). Jednak nie mogłem go w ogóle uruchomić za pomocą document.body.addEventListener… nie wiem dlaczego. Zamiast tego uzyskałem ten sam wynik z window.addEventListener.
WoodenKitty,

3
To nie działa w Firefoksie i Edge dla mnie. Jednak słuchając na mousemovewydarzenia na window.document w uzupełnieniu do słuchania focusna windowzdaje się wszędzie działa (przynajmniej w nowoczesnych przeglądarkach, nie dbam o wrakach przeszłość dekady). Zasadniczo do tego zadania można użyć dowolnego zdarzenia interakcji, które jest blokowane przez otwarte okno dialogowe otwierania pliku.
John Weisz,

@WoodenKitty jak zaimplementowałeś to za pomocą addEventListener. Próbuję to zrobić w kątowym i dokumencie. Nikt też dla mnie nie pracuje
Terrance Jackson

to nie działa w przypadku ukrytych pól wejściowych.
Sebastian

20

Nie możesz.

Wynik okna dialogowego pliku nie jest widoczny dla przeglądarki.


Czy można sprawdzić, czy selektor plików jest otwarty?
Ppp

1
@Ppp - Nie. Przeglądarka ci tego nie powie.
Oded

6
„Wynik okna dialogowego pliku nie jest widoczny dla przeglądarki”. Nie jest to prawdą, jeśli okno dialogowe jest zamykane przez wybranie pliku.
Trevor

3
Zmiany wywołane za pomocą okna wyboru pliku jest narażona na przeglądarce. Zobacz, jeśli nie wybrano żadnego pliku i żaden plik nie został wybrany później, to nic się nie zmieniło, więc żadne changezdarzenie nie jest wysyłane.
John Weisz

1
Ponadto, jeśli plik jest już zaznaczony i ponownie wybierzesz ten sam plik, żadne changezdarzenie nie zostanie z tego powodu wywołane.
Patrick Roberts,

16

Więc rzucę kapelusz na to pytanie, ponieważ wymyśliłem nowatorskie rozwiązanie. Mam progresywną aplikację internetową, która umożliwia użytkownikom robienie zdjęć i filmów oraz przesyłanie ich. Używamy WebRTC, gdy jest to możliwe, ale wracamy do selektorów plików HTML5 dla urządzeń z mniejszą obsługą * kaszel Safari kaszel *. Jeśli pracujesz konkretnie na mobilnej aplikacji internetowej na Androida / iOS, która używa natywnego aparatu do bezpośredniego przechwytywania zdjęć / filmów, jest to najlepsze rozwiązanie, z którym się spotkałem.

Sedno tego problemu polega na tym, że kiedy strona się ładuje, to filejest null, ale kiedy użytkownik otworzy okno dialogowe i naciśnie "Anuluj", filenadal jest null, więc nie zmienił się, więc nie jest wyzwalane żadne zdarzenie "zmień". W przypadku komputerów stacjonarnych nie jest to takie złe, ponieważ większość interfejsów użytkownika komputerów stacjonarnych nie jest zależnych od wiedzy, kiedy wywoływane jest anulowanie, ale mobilne interfejsy użytkownika, które uruchamiają aparat w celu zrobienia zdjęcia / wideo, są bardzo zależne od wiedzy, kiedy naciśnięto anulowanie.

Początkowo użyłem document.body.onfocuszdarzenia do wykrycia, kiedy użytkownik wrócił z selektora plików i działało to na większości urządzeń, ale iOS 11.3 zepsuł je, ponieważ to zdarzenie nie jest wyzwalane.

Pojęcie

Moim rozwiązaniem jest * drżenie *, aby zmierzyć taktowanie procesora w celu określenia, czy strona jest obecnie na pierwszym planie, czy w tle. Na urządzeniach mobilnych czas przetwarzania jest przypisywany aplikacji znajdującej się obecnie na pierwszym planie. Kiedy kamera jest widoczna, kradnie czas procesora i pozbawia przeglądarkę priorytetów. Wszystko, co musimy zrobić, to zmierzyć, ile czasu przetwarzania zajmuje nasza strona, po uruchomieniu aparatu nasz dostępny czas drastycznie spadnie. Kiedy kamera jest zamknięta (anulowana lub w inny sposób), nasz dostępny czas rośnie.

Realizacja

Możemy zmierzyć taktowanie procesora za pomocą setTimeout()wywołania wywołania zwrotnego w X milisekund, a następnie zmierzyć, ile czasu zajęło jego rzeczywiste wywołanie. Przeglądarka nigdy nie wywoła go dokładnie po X milisekundach, ale jeśli jest to rozsądne blisko, to musimy być na pierwszym planie. Jeśli przeglądarka jest bardzo daleko (ponad 10x wolniej niż żądano), musimy znajdować się w tle. Podstawowa implementacja tego jest następująca:

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

Przetestowałem to na najnowszej wersji iOS i Androida, wywołując natywną kamerę, ustawiając atrybuty <input />elementu.

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

To działa o wiele lepiej, niż się spodziewałem. Uruchamia 10 prób, żądając wywołania licznika czasu w ciągu 25 milisekund. Następnie mierzy, ile czasu faktycznie zajęło wywołanie, a jeśli średnia z 10 prób jest mniejsza niż 50 milisekund, zakładamy, że musimy być na pierwszym planie, a kamera zniknęła. Jeśli jest dłuższy niż 50 milisekund, to nadal musimy być w tle i powinniśmy nadal czekać.

Kilka dodatkowych szczegółów

Użyłem setTimeout()raczej niż setInterval()dlatego, że ta ostatnia może kolejkować wiele wywołań, które są wykonywane natychmiast po sobie. Może to drastycznie zwiększyć szum w naszych danych, więc utknąłem przy setTimeout()tym, mimo że jest to trochę bardziej skomplikowane.

Te konkretne liczby działały dobrze dla mnie, chociaż widziałem przynajmniej raz przypadek, w którym odrzucenie kamery zostało wykryte przedwcześnie. Uważam, że dzieje się tak, ponieważ kamera może się otwierać powoli, a urządzenie może przeprowadzić 10 prób, zanim faktycznie przejdzie w tło. Dodanie kolejnych prób lub odczekanie około 25-50 milisekund przed uruchomieniem tej funkcji może być obejściem tego problemu.

Pulpit

Niestety, to nie działa tak naprawdę w przeglądarkach komputerowych. W teorii ta sama sztuczka jest możliwa, ponieważ priorytetyzują bieżącą stronę względem stron w tle. Jednak wiele komputerów ma wystarczającą ilość zasobów, aby strona działała z pełną prędkością, nawet w tle, więc ta strategia tak naprawdę nie działa w praktyce.

Alternatywne rozwiązania

Jednym z alternatywnych rozwiązań, o których niewiele osób wspomina, było kpienie z pliku FileList. Zaczynamy nullod, <input />a następnie, jeśli użytkownik otworzy kamerę i anuluje, do nullktórego wróci , co nie jest zmianą i żadne zdarzenie nie zostanie wywołane. Jednym z rozwiązań byłoby przypisanie fikcyjnego pliku do <input />początku strony, dlatego ustawienie na nullbyłoby zmianą, która wywołałaby odpowiednie zdarzenie.

Niestety nie ma możliwości oficjalnego utworzenia a FileList, a <input />element wymaga FileListw szczególności a i nie przyjmie żadnej innej wartości poza null. Oczywiście FileListobiektów nie można bezpośrednio konstruować, zrobić to z jakimś starym problemem bezpieczeństwa, który najwyraźniej nie ma już znaczenia. Jedynym sposobem na złapanie jednego z zewnątrz <input />elementu jest użycie hacka, który kopiuje i wkleja dane, aby sfałszować zdarzenie ze schowka, które może zawierać FileListobiekt (w zasadzie udajesz przeciągnij i upuść-plik -Twoja-witryna). Jest to możliwe w Firefoksie, ale nie w iOS Safari, więc nie było to opłacalne w moim konkretnym przypadku użycia.

Przeglądarki, proszę ...

Nie trzeba dodawać, że jest to ewidentnie śmieszne. Fakt, że strony internetowe nie otrzymują żadnego powiadomienia o zmianie krytycznego elementu interfejsu użytkownika, jest po prostu śmieszny. To jest naprawdę błąd w specyfikacji, ponieważ nigdy nie był przeznaczony do pełnoekranowego interfejsu użytkownika do przechwytywania multimediów, a niewywołanie zdarzenia „zmiana” jest technicznie zgodne ze specyfikacją.

Czy jednak dostawcy przeglądarek mogą rozpoznać rzeczywistość? Można to rozwiązać za pomocą nowego zdarzenia „done”, które jest wyzwalane nawet wtedy, gdy żadna zmiana nie następuje, lub można po prostu wywołać „zmianę” i tak. Tak, byłoby to sprzeczne ze specyfikacją, ale wydedukowanie zdarzenia zmiany po stronie JavaScript jest trywialne, ale zasadniczo niemożliwe jest wymyślenie własnego zdarzenia „done”. Nawet moje rozwiązanie to tak naprawdę tylko heurystyka, o ile nie daje żadnej gwarancji na stan przeglądarki.

W obecnej postaci ten interfejs API jest zasadniczo bezużyteczny dla urządzeń mobilnych i myślę, że stosunkowo prosta zmiana przeglądarki może uczynić to nieskończenie łatwiejszym dla programistów internetowych * zejdzie z mydelniczki *.



7

Kiedy wybierzesz plik i klikniesz otwórz / anuluj, inputelement powinien stracić fokus blur. Zakładając, że początkowa valuewartość inputjest pusta, każda niepusta wartość w programie blurobsługi oznaczałaby OK, a pusta wartość oznaczałaby Anulowanie.

UPDATE: Nie blurjest wyzwalane, gdy inputjest ukryty. Więc nie można użyć tej sztuczki z przesyłaniem opartym na IFRAME, chyba że chcesz tymczasowo wyświetlić plik input.


3
W Firefoksie 10 beta i Chrome 16 blurnie jest uruchamiany po wybraniu pliku. Nie próbowałem w innych przeglądarkach.
Blaise,

1
To nie działa - rozmycie na wejściu jest wyzwalane natychmiast po kliknięciu. Chrome 63.0.3239.84 x64 na 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
Czy sprawdziłeś, czy Twoje elseoświadczenie kiedykolwiek zostanie ocenione?
jayarjo

O ile pamiętam, kiedy napisałem tę odpowiedź, jeśli użytkownik wybierze plik, a następnie ponownie wybierze plik (ale potem anuluje), zostanie on potraktowany jako żaden plik nie wybrany, używam alertów tylko przy zmiennej selected_file_name, aby go przetestować. w każdym razie, od tamtej pory już go nie testowałem.
Rizky

4
Mogę powiedzieć, po kilku testach w różnych przeglądarkach, że Firefox nie uruchamia zdarzenia zmiany przy anulowaniu, ani nie czyści istniejących plików wybranych po ponownym otwarciu i kliknięciu anulowania po raz drugi. Dziwne, co?
mix3d

7

Większość z tych rozwiązań u mnie nie działa.

Problem polega na tym, że nigdy nie wiadomo, które zdarzenie zostanie wywołane pięścią, czy tak jest, clickczy tak jest change? Nie możesz zakładać żadnej kolejności, ponieważ prawdopodobnie zależy to od implementacji przeglądarki.

Przynajmniej w Operze i Chrome (koniec 2015 r.) clickJest uruchamiany tuż przed „wypełnieniem” danych wejściowych plikami, więc nigdy nie będziesz wiedzieć, ile czasu minie, files.length != 0zanim clickzostanie wywołany po change.

Oto kod:

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

Jeśli klikniesz na stronę ... "Kliknięto Anuluj" ... = | (Testowane z Chrome)
Eduardo Lucio

5

Po prostu posłuchaj również zdarzenia kliknięcia.

Idąc za przykładem Shiboe, oto przykład 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!');
    }

});

Możesz to zobaczyć w akcji tutaj: http://jsfiddle.net/T3Vwz


1
Testowałem na Chrome 51, nie działa przy pierwszym wybraniu plików. Rozwiązaniem jest dodanie opóźnienia: jsfiddle.net/7jfbmunh
Benoit Blanchon

Twoje skrzypce nie działały dla mnie - używając Firefoksa 58.0.2
barrypicker

4

Możesz złapać anulowanie, jeśli wybierzesz ten sam plik co poprzednio i klikniesz anuluj: w tym przypadku.

Możesz to zrobić w ten sposób:

<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 ten post naprawdę mi pomógł.
VPK

4
Niezupełnie, jeśli użytkownik wybierze ten sam plik, nie jest to anulowanie = (
Tiago Peres França

13
wymiana płonie tylko wtedy, gdy nastąpiła zmiana. Więc jeśli plik jest tym samym plikiem co poprzednio, nasłuchiwanie onchange nie uruchomi się.
Shane

1
Możesz wyczyścić wybór wejścia (input.value = '') po wykryciu pierwszej zmiany, dzięki czemu druga zmiana zostanie uruchomiona, nawet jeśli użytkownik wybierze ten sam plik ponownie.
Chris,

1
@CodeGems można programowo ustawić input.value na pusty ciąg. Ale masz rację, że ustawienie jej na inną wartość jest niedozwolone.
Chris,

4

Najłatwiej jest sprawdzić, czy w pamięci tymczasowej są jakieś pliki. Jeśli chcesz uzyskać zdarzenie zmiany za każdym razem, gdy użytkownik kliknie plik wejściowy, możesz je wywołać.

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

Rozwiązanie Shiboe byłoby dobre, gdyby działało na mobilnym zestawie internetowym, ale tak nie jest. To, co mogę wymyślić, to dodanie detektora zdarzeń mousemove do jakiegoś obiektu dom w momencie otwarcia okna wejściowego pliku, na przykład:

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

Zdarzenie mousemove jest blokowane na stronie, gdy okno dialogowe pliku jest otwarte, a po jego zamknięciu sprawdza, czy w pliku wejściowym znajdują się jakieś pliki. W moim przypadku chcę, aby wskaźnik aktywności blokował rzeczy do momentu przesłania pliku, więc chcę usunąć mój wskaźnik tylko po anulowaniu.

Jednak to nie rozwiązuje problemu w przypadku urządzeń mobilnych, ponieważ nie ma myszy do poruszania. Moje rozwiązanie nie jest doskonałe, ale myślę, że jest wystarczająco dobre.

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

Teraz nasłuchujemy dotknięcia ekranu, aby sprawdzić te same pliki. Jestem pewien, że palec użytkownika zostanie umieszczony na ekranie dość szybko po anulowaniu i odrzuceniu tego wskaźnika aktywności.

Można również po prostu dodać wskaźnik aktywności do zdarzenia zmiany danych wejściowych pliku, ale na telefonie komórkowym często występuje kilkusekundowe opóźnienie między wyborem obrazu a uruchomieniem zdarzenia zmiany, więc jest to po prostu znacznie lepszy UX, aby wskaźnik aktywności był wyświetlany w rozpoczęcie procesu.


2

Znalazłem ten atrybut, jak dotąd najprostszy.

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

Tutaj selectedFilejest input type=file.


To dla mnie dobre rozwiązanie, poza tym, że powinno być> = 1
Bron Thulke

2

Wiem, że to bardzo stare pytanie, ale na wszelki wypadek, gdyby komuś pomogło, stwierdziłem, używając zdarzenia onmousemove do wykrycia anulowania, że ​​konieczne było przetestowanie dwóch lub więcej takich zdarzeń w krótkim czasie. Wynika to z faktu, że pojedyncze zdarzenia onmousemove są generowane przez przeglądarkę (Chrome 65) za każdym razem, gdy kursor jest przenoszony z okna dialogowego wyboru pliku i za każdym razem, gdy jest przenoszony z okna głównego i z powrotem. Prosty licznik zdarzeń ruchu myszy w połączeniu z krótkim limitem czasu, aby zresetować licznik z powrotem do zera, działał niewiarygodnie.


1
Pierwszy akapit jest dobrą odpowiedzią, mimo że można go podzielić na kilka dla lepszej czytelności. Ale te dwie linie „SOAPBOX” sprawiają, że graniczy to z brakiem odpowiedzi, ponieważ pytania nie należą do odpowiedzi. Czy możesz trochę przeformułować / przeformułować swoją odpowiedź? Dziękuję Ci.
Filnor

1

W moim przypadku musiałem ukryć przycisk przesyłania, gdy użytkownicy wybierali obrazy.

Oto, co wymyśliłem:

$(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-fieldto mój selektor plików. Kiedy ktoś na nią kliknie, wyłączam przycisk przesyłania formularza. Chodzi o to, że kiedy okno dialogowe pliku zostało zamknięte - nie ma znaczenia, że ​​wybierają plik lub anulują - #image-fieldwróciło fokus, więc słucham tego zdarzenia.

AKTUALIZACJA

Odkryłem, że to nie działa w safari i poltergeist / phantomjs. Weź te informacje pod uwagę, jeśli chcesz je wdrożyć.


1

Łącząc rozwiązania Shiboe i alx, otrzymałem najbardziej niezawodny kod:

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

Generalnie zdarzenie mousemove załatwia sprawę, ale w przypadku, gdy użytkownik kliknął, a następnie:

  • anulowano okno dialogowe otwierania pliku klawiszem Escape (bez poruszania myszą), wykonałem kolejne dokładne kliknięcie, aby ponownie otworzyć okno dialogowe pliku ...
  • przełączył fokus na dowolną inną aplikację, potem wrócił do okna dialogowego otwierania pliku przeglądarki i zamknął je, a następnie otworzył ponownie za pomocą klawisza Enter lub spacji ...

... nie otrzymamy zdarzenia mousemove, dlatego nie można anulować oddzwonienia. Co więcej, jeśli użytkownik anuluje drugie okno dialogowe i wykona ruch myszą, otrzymamy 2 anulowane wywołania zwrotne. Na szczęście w obu przypadkach w dokumencie pojawia się specjalne wydarzenie focusIn jQuery, pomagając nam uniknąć takich sytuacji. Jedynym ograniczeniem jest zablokowanie zdarzenia focusIn.


Złapałem mousemovezdarzenie documentpodczas wybierania plików w oknie dialogowym systemu operacyjnego w Chrome 56.0.2924.87 na Ubuntu 16.10. Nie ma problemu, gdy nie poruszasz myszą lub używasz tylko klawiatury do wybrania pliku. Musiałem wymienić mousemovena, keydownaby wykryć anulowanie tak wcześnie, jak to możliwe, ale nie „za wcześnie”, gdy okno dialogowe było nadal otwarte.
Ferdinand Prantl

1

Widzę, że moja odpowiedź byłaby dość nieaktualna, ale nigdy mniej. Miałem ten sam problem. Oto moje rozwiązanie. Najbardziej przydatnym wyciętym kodem był ten z KGA. Ale to nie do końca działa i jest nieco skomplikowane. Ale uprościłem to.

Ponadto głównym problemem był fakt, że wydarzenie „zmiany” nie następuje natychmiast po skupieniu, więc musimy trochę poczekać.

„#appendfile” - który użytkownik klika, aby dołączyć nowy plik. Hrefs otrzymują zdarzenia fokusu.

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

Możesz to wykryć tylko w ograniczonych okolicznościach. W szczególności w chrome, jeśli plik został wybrany wcześniej, a następnie kliknięto okno dialogowe pliku i kliknięto anulowanie, Chrome czyści plik i uruchamia zdarzenie onChange.

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

W tym scenariuszu można to wykryć, obsługując zdarzenie onChange i sprawdzając właściwość files .


1

Wydaje się, że działa dla mnie (na komputerze stacjonarnym, 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;
}

Używa to angularjs $ q, ale w razie potrzeby powinieneś być w stanie zastąpić go dowolnym innym frameworkiem obietnicy.

Testowane na IE11, Edge, Chrome, Firefox, ale nie wydaje się działać w Chrome na tablecie z Androidem, ponieważ nie uruchamia zdarzenia Focus.


1

Pole filetypu -type, frustrujące, nie reaguje na wiele zdarzeń (rozmycie byłoby cudowne). Widzę, że wiele osób sugeruje changerozwiązania zorientowane na orientację i są one odrzucane.

changedziała, ale ma poważną wadę (w porównaniu z tym, co chcemy, aby się stało).

Gdy świeżo załadujesz stronę zawierającą pole pliku, otwórz okno i naciśnij anuluj. Nic, irytująco, się nie zmienia .

To, co wybrałem, to ładowanie w stanie zamkniętym.

  • Kolejna część formularza a section#after-imagew moim przypadku jest niewidoczna. Kiedy moje file fieldzmiany, pojawia się przycisk przesyłania. Po pomyślnym przesłaniu section#after-imagezostanie wyświetlony symbol.
  • Jeśli użytkownik ładuje, otwiera okno dialogowe pliku, a następnie anuluje, nigdy nie widzi przycisku przesyłania.
  • Jeśli użytkownik wybierze plik, pojawi się przycisk przesyłania. Jeśli następnie otworzą okno dialogowe i anulują, changezdarzenie jest wyzwalane przez to anulowanie i tam mogę (i robię) ponownie ukryć mój przycisk przesyłania, dopóki nie zostanie wybrany właściwy plik.

Miałem szczęście, że ten stan z bramą był już projektem mojej formy. Nie musisz używać tego samego stylu, wystarczy, że przycisk przesyłania będzie początkowo ukryty, a po zmianie ustaw ukryte pole lub zmienną javascript na coś, co możesz monitorować po przesłaniu.

Próbowałem zmienić wartość plików [0] przed interakcją z polem. To nic nie zmieniło w przypadku wymiany.

Więc tak, changedziała, przynajmniej tak dobre, jak zamierzamy. Pole plików jest zabezpieczone z oczywistych powodów, ale ku frustracji programistów o dobrych intencjach.

To nie pasuje do mojego celu, ale możesz być w stanie onclickzaładować ostrzeżenie (nie alert(), ponieważ powoduje to wstrzymanie przetwarzania strony) i ukryć je, jeśli zostanie wywołana zmiana, a pliki [0] są puste. Jeśli zmiana nie zostanie wyzwolona, ​​element div pozostaje w swoim stanie.


0

Jest na to hakerski sposób (dodaj wywołania zwrotne lub rozwiąż niektóre implementacje odroczonych / obietnic zamiast alert()połączeń):

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

Jak to działa: gdy okno dialogowe wyboru pliku jest otwarte, dokument nie otrzymuje zdarzeń wskaźnika myszy. Opóźnienie 1000 ms pozwala na faktyczne pojawienie się okna dialogowego i zablokowanie okna przeglądarki. Sprawdzone w Chrome i Firefox (tylko Windows).

Ale oczywiście nie jest to niezawodny sposób wykrywania anulowanych dialogów. Chociaż może poprawić niektóre zachowanie interfejsu użytkownika.


Jedną z głównych wad jest to, że telefony i tablety nie używają myszy normalnie!
Peter,

0

Oto moje rozwiązanie, używając fokusa wejścia pliku (bez użycia żadnych timerów)

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


Uruchomienie twojego fragmentu Error: { "message": "Uncaught SyntaxError: missing ) after argument list", "filename": "https://stacksnippets.net/js", "lineno": 51, "colno": 1 }
kodu

0

W najlepszym razie jest to hackerskie, ale oto działający przykład mojego rozwiązania, które wykrywa, czy użytkownik przesłał plik, i zezwala mu na kontynuację tylko wtedy, gdy przesłał plik.

Zasadniczo ukryć Continue, Save, Proceedlub niezależnie przycisk jest. Następnie w JavaScript pobierasz nazwę pliku. Jeśli nazwa pliku nie ma wartości, nie pokazuj Continueprzycisku. Jeśli ma wartość, pokaż przycisk. Działa to również, jeśli najpierw prześlą plik, a następnie spróbują przesłać inny plik i klikną anuluj.

Oto kod.

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

W przypadku CSS wiele z tego polega na tym, aby witryna i przycisk były dostępne dla każdego. Zaprojektuj swój przycisk tak, jak lubisz.


0

Cóż, to nie odpowiada dokładnie na twoje pytanie. Moje założenie jest takie, że masz scenariusz, kiedy dodajesz dane wejściowe do pliku i wywołujesz wybór pliku, a jeśli użytkownik kliknie przycisk Anuluj, po prostu usuwasz dane wejściowe.

W takim przypadku: Po co dodawać puste dane wejściowe do pliku?

Utwórz go w locie, ale dodaj go do DOM tylko wtedy, gdy jest wypełniony. Na przykład:

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

Więc tutaj tworzę <input type = "file" /> w locie, wiążę się z jego zdarzeniem zmiany, a następnie natychmiast wywołuję kliknięcie. Zmiana będzie uruchamiana tylko wtedy, gdy użytkownik wybierze plik i kliknie OK, w przeciwnym razie dane wejściowe nie zostaną dodane do DOM, a zatem nie zostaną przesłane.

Przykład roboczy tutaj: https://jsfiddle.net/69g0Lxno/3/


0

// Użyj najechania zamiast rozmycia

var fileInput = $("#fileInput");

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

    //open
} else {

}

0

Jeśli już potrzebujesz JQuery, to rozwiązanie może zadziałać (jest to dokładnie ten sam kod, którego faktycznie potrzebowałem w moim przypadku, chociaż użycie Promise to po prostu wymuszenie na kodzie czekania, aż wybór pliku zostanie rozwiązany):

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

W tym wątku proponowanych jest kilka rozwiązań, a ta trudność w wykryciu, kiedy użytkownik kliknie przycisk „Anuluj” w oknie wyboru pliku, jest problemem, który dotyka wiele osób.

Faktem jest, że nie ma w 100% niezawodnego sposobu na wykrycie, czy użytkownik kliknął przycisk „Anuluj” w oknie wyboru pliku. Istnieją jednak sposoby niezawodnego wykrywania, czy użytkownik dodał plik do pliku wejściowego. To jest podstawowa strategia tej odpowiedzi!

Postanowiłem dodać tę odpowiedź, ponieważ najwyraźniej inne odpowiedzi nie działają w większości przeglądarek ani nie są gwarantowane na urządzeniach mobilnych.

Krótko mówiąc, kod opiera się na 3 punktach:

  1. Plik wejściowy jest początkowo tworzony dynamicznie w "pamięci" w js (w tej chwili nie dodajemy go do "HTML");
  2. Po dodaniu pliku plik wejściowy jest dodawany do HTML, w przeciwnym razie nic się nie dzieje;
  3. Usunięcie pliku odbywa się poprzez usunięcie pliku wejściowego z HTML przez określone zdarzenie, co oznacza, że ​​„edycja” / „modyfikacja” pliku odbywa się poprzez usunięcie starego pliku wejściowego i utworzenie nowego.

Aby lepiej zrozumieć, spójrz na poniższy kod i notatki.

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

}

Dzięki! = D


0

Osiągnęliśmy w kątowym jak poniżej.

    1. zdarzenie wiązania kliknięcia w pliku typu wejściowego.
      1. Dołącz zdarzenie fokusu do okna i dodaj warunek, jeśli uploadPanel ma wartość true, a następnie pokaż konsolę.
      2. po kliknięciu pliku typu wejściowego wartość logiczna uploadPanel ma wartość true. i pojawi się okno dialogowe.
      3. po anulowaniu LUB przycisk Esc kliknij, a pojawi się okno dialogowe ambulatorium i konsola.

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

Po prostu dodaj odbiornik „zmień” do swojego wejścia, którego typ to plik. to znaczy

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

Zrobiłem już przy użyciu jQuery i oczywiście każdy może używać Valina JS (zgodnie z wymaganiami).

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

            if (this.files.length) {

            alert('file choosen');

            } else {

            alert('file NOT choosen');

            }

    });

.change()nie jest wywoływana niezawodnie, jeśli użytkownik kliknie „Anuluj” na selektorze plików.
Ben Wheeler

@benWheeler, sprawdziłem to zarówno na Chrome, jak i Firefoxie i działało, ale nie było testowane na innych przeglądarkach.
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>

Odpowiedzi zawierające tylko kod są uważane za niskiej jakości: pamiętaj, aby wyjaśnić, co robi Twój kod i jak rozwiązuje problem. Pomoże to pytającemu i przyszłym czytelnikom, jeśli możesz dodać więcej informacji w swoim poście. Zobacz Wyjaśnianie odpowiedzi opartych na kodzie
Calos

0

Rozwiązanie do wyboru plików z ukrytym wejściem

Uwaga: ten kod nie wykrywa anulowania, ale oferuje sposób na obejście potrzeby wykrycia go w typowym przypadku, gdy ludzie próbują go wykryć.

Dotarłem tutaj, szukając rozwiązania do przesyłania plików za pomocą ukrytego wejścia, uważam, że jest to najczęstszy powód, aby szukać sposobu na wykrycie anulowania wejścia pliku (otwórz okno dialogowe pliku -> jeśli plik został wybrany, uruchom jakiś kod, inaczej nic nie rób), oto moje rozwiązanie:

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

Przykład użycia:

Zauważ, że jeśli żaden plik nie został wybrany, pierwsza linia zwróci się tylko raz, gdy selectFile()zostanie wywołana ponownie (lub jeśli zadzwoniłeś fileSelectorResolve()z innego miejsca).

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

Inny przykład:

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

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

W Chrome 83.0 nie otrzymuję wywołania zwrotnego dla zdarzenia wejściowego, gdy użytkownik anuluje, a także nie przy drugiej próbie
Soylent Graham

1
@SoylentGraham Tak, ponieważ ten kod nie wykrywa anulowania, oferuje sposób na obejście potrzeby wykrycia go w typowym przypadku, gdy ludzie próbują go wykryć. Ponieważ nie byłam tego jasna, dodaję ten komentarz do mojej odpowiedzi.
ziemniak

Myślę, że źle odczytałem Twoją odpowiedź, ponieważ „mówi, że użytkownik anulował przy drugiej próbie”
Soylent Graham

-1

Możesz ustawić nasłuchiwanie zmian jQuery w polu wejściowym i wykryć, że użytkownik anulował lub zamknął okno przesyłania na podstawie wartości pola.

Oto przykład:

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

Nie mam pojęcia, dlaczego jest to odrzucane - właśnie tego szukałem. Dziękuję Ci! 🙂
user1274820
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.