Odpowiedzi:
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
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
.
mousemove
wydarzenia na window.document
w uzupełnieniu do słuchania focus
na window
zdaje 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.
Nie możesz.
Wynik okna dialogowego pliku nie jest widoczny dla przeglądarki.
change
zdarzenie nie jest wysyłane.
change
zdarzenie nie zostanie z tego powodu wywołane.
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 file
jest null
, ale kiedy użytkownik otworzy okno dialogowe i naciśnie "Anuluj", file
nadal 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.onfocus
zdarzenia 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.
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.
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ć.
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.
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.
Jednym z alternatywnych rozwiązań, o których niewiele osób wspomina, było kpienie z pliku FileList
. Zaczynamy null
od, <input />
a następnie, jeśli użytkownik otworzy kamerę i anuluje, do null
któ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 null
byłoby zmianą, która wywołałaby odpowiednie zdarzenie.
Niestety nie ma możliwości oficjalnego utworzenia a FileList
, a <input />
element wymaga FileList
w szczególności a i nie przyjmie żadnej innej wartości poza null
. Oczywiście FileList
obiektó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ć FileList
obiekt (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.
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 *.
Kiedy wybierzesz plik i klikniesz otwórz / anuluj, input
element powinien stracić fokus blur
. Zakładając, że początkowa value
wartość input
jest pusta, każda niepusta wartość w programie blur
obsługi oznaczałaby OK, a pusta wartość oznaczałaby Anulowanie.
UPDATE: Nie blur
jest wyzwalane, gdy input
jest ukryty. Więc nie można użyć tej sztuczki z przesyłaniem opartym na IFRAME, chyba że chcesz tymczasowo wyświetlić plik input
.
blur
nie jest uruchamiany po wybraniu pliku. Nie próbowałem w innych przeglądarkach.
/* 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
oświadczenie kiedykolwiek zostanie ocenione?
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, click
czy 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.) click
Jest uruchamiany tuż przed „wypełnieniem” danych wejściowych plikami, więc nigdy nie będziesz wiedzieć, ile czasu minie, files.length != 0
zanim click
zostanie 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);
};
});
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
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>
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
}
});
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.
Znalazłem ten atrybut, jak dotąd najprostszy.
if ($('#selectedFile')[0].files.length > 1)
{
// Clicked on 'open' with file
} else {
// Clicked on 'cancel'
}
Tutaj selectedFile
jest input type=file
.
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.
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-field
to 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-field
wró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ć.
Łą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:
... 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.
mousemove
zdarzenie document
podczas 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ć mousemove
na, keydown
aby wykryć anulowanie tak wcześnie, jak to możliwe, ale nie „za wcześnie”, gdy okno dialogowe było nadal otwarte.
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);
});
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 .
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.
Pole file
typu -type, frustrujące, nie reaguje na wiele zdarzeń (rozmycie byłoby cudowne). Widzę, że wiele osób sugeruje change
rozwiązania zorientowane na orientację i są one odrzucane.
change
dział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.
section#after-image
w moim przypadku jest niewidoczna. Kiedy moje file field
zmiany, pojawia się przycisk przesyłania. Po pomyślnym przesłaniu section#after-image
zostanie wyświetlony symbol.change
zdarzenie 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, change
dział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 onclick
zał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.
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.
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) {
// ...
}
Error: { "message": "Uncaught SyntaxError: missing ) after argument list", "filename": "https://stacksnippets.net/js", "lineno": 51, "colno": 1 }
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
, Proceed
lub niezależnie przycisk jest. Następnie w JavaScript pobierasz nazwę pliku. Jeśli nazwa pliku nie ma wartości, nie pokazuj Continue
przycisku. 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.
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/
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();
});
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:
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
Osiągnęliśmy w kątowym jak poniżej.
<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();
}
}
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.
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>
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'));
});
}
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 ...
}
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
}
});
e.target.files