Dosya girişinde iptal tıklandığında nasıl tespit edilir?


112

Kullanıcının bir html dosya girişi kullanarak bir dosya girişini iptal ettiğini nasıl tespit edebilirim?

onChange, bir dosya seçtiklerini algılamama izin veriyor, ancak ne zaman iptal ettiklerini de bilmek istiyorum (hiçbir şey seçmeden dosya seçme iletişim kutusunu kapatın).


Birinin dosyayı seçip e.target.files
seçmediğini

Yanıtlar:


45

Doğrudan bir çözüm olmasa da, yalnızca (test ettiğim kadarıyla) onfocus ile çalıştığı için kötü olsa da (oldukça sınırlayıcı bir olay engelleme gerektirir) bunu aşağıdakilerle başarabilirsiniz:

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

Bunun güzel yanı, onu dosya olayıyla zamanında ekleyebilmeniz / ayırabilmeniz ve ayrıca gizli girişlerle iyi çalışıyor gibi görünmesi (berbat varsayılan giriş türü için görsel bir geçici çözüm kullanıyorsanız kesin bir avantaj = ') dosya'). Bundan sonra, giriş değerinin değişip değişmediğini bulmanız yeterlidir.

Bir örnek:

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

İş başında görün: http://jsfiddle.net/Shiboe/yuK3r/6/

Ne yazık ki, yalnızca webkit tarayıcılarında çalışıyor gibi görünüyor. Belki başka biri Firefox / IE çözümünü bulabilir


Odaklanma olayının her seferinde ateşlenmesini istemedim, bu yüzden kullanmak istedim addEventListener("focus",fn,{once: true}). Ancak onu kullanarak ateşlemeyi hiç document.body.addEventListenerbaşaramadım .. Nedenini bilmiyorum. Bunun yerine ile aynı sonucu aldım window.addEventListener.
WoodenKitty

3
Bu benim için Firefox ve Edge'de çalışmıyor. Ancak, dinlemeye mousemoveüzerine olay window.document ilaveten dinlemeye focusüzerine window(modern tarayıcılarda en azından ben son on yıl batıklar umurumda değil) her yerde çalışıyor gibi görünüyor. Temel olarak, bu görev için açık bir dosya açma iletişim kutusu tarafından engellenen herhangi bir etkileşim olayı kullanılabilir.
John Weisz

@WoodenKitty bunu addEventListener kullanarak nasıl uyguladınız? Bunu açısal olarak yapmaya çalışıyorum ve document.body de benim için çalışmıyor
Terrance Jackson

bu gizli giriş alanlarında çalışmaz.
Sebastian

20

Yapamazsın.

Dosya iletişim kutusunun sonucu tarayıcı tarafından gösterilmez.


Dosya seçicinin açık olup olmadığını kontrol etmek mümkün mü?
Ppp

1
@Ppp - Hayır. Tarayıcı size bunu söylemiyor.
Oded

6
"Dosya iletişim kutusunun sonucu tarayıcı tarafından gösterilmez." İletişim kutusu bir dosya seçilerek kapatılırsa bu doğru değildir.
Trevor

3
Değişim dosya iletişim kutusunu kullanarak tetiklenen tarayıcıya maruz kalmaktadır. Bakın, eğer seçili dosya yoksa ve daha sonra hiçbir dosya seçilmezse, o zaman hiçbir şey değişmemiş, dolayısıyla hiçbir changeolay gönderilmemiştir.
John Weisz

1
Ek olarak, bir dosya zaten seçilmişse ve aynı dosyayı tekrar seçerseniz, bunun için de bir changeolay gönderilmez.
Patrick Roberts

16

Bu yüzden, yeni bir çözüm bulduğum için bu soruya şapkamı atacağım. Kullanıcıların fotoğraf ve video çekmesine ve bunları yüklemesine olanak tanıyan Progressive Web Uygulamam var. Mümkün olduğunda WebRTC kullanıyoruz, ancak daha az destek * öksürük Safari öksürüğü * olan cihazlar için HTML5 dosya seçicilere geri dönüyoruz. Doğrudan fotoğraf / video çekmek için yerel kamerayı kullanan bir Android / iOS mobil web uygulamasında özellikle çalışıyorsanız, o zaman karşılaştığım en iyi çözüm budur.

Bu sorunun özü, sayfa yüklendiğinde, fileolur null, ancak daha sonra kullanıcı iletişim kutusunu açıp "İptal" e bastığında file, hala null"değişmez", dolayısıyla "değişiklik" olayının tetiklenmemesidir. Masaüstü bilgisayarlar için bu çok da kötü değil çünkü çoğu masaüstü kullanıcı arayüzü bir iptalin ne zaman çağrıldığını bilmeye bağlı değil, ancak kamerayı bir fotoğraf / video çekmek için getiren mobil kullanıcı arayüzleri , iptalin ne zaman basıldığını bilmeye çok bağlı.

Aslında document.body.onfocusolayı, kullanıcının dosya seçiciden ne zaman döndüğünü algılamak için kullandım ve bu çoğu cihaz için işe yaradı, ancak iOS 11.3, bu olay tetiklenmediğinden bunu bozdu.

kavram

Buna çözümüm, sayfanın o anda ön planda mı yoksa arka planda mı olduğunu belirlemek için CPU zamanlamasını ölçmek için * titreme *. Mobil cihazlarda, şu anda ön plandaki uygulamaya işlem süresi verilir. Bir kamera göründüğünde, CPU zamanını çalar ve tarayıcının önceliğini kaldırır. Tek yapmamız gereken, sayfamıza ne kadar işlem süresi verildiğini ölçmek, kamera başladığında kullanılabilir süremiz büyük ölçüde düşecek. Kamera kapatıldığında (iptal edildiğinde veya başka şekilde), mevcut zamanımız tekrar yükselir.

uygulama

setTimeout()X milisaniye cinsinden bir geri aramayı çağırmak için kullanarak CPU zamanlamasını ölçebilir ve ardından gerçekten çağırmanın ne kadar sürdüğünü ölçebiliriz. Tarayıcı asla tam olarak X milisaniyeden sonra onu çağırmaz, ancak makul bir yakınlık söz konusuysa, o zaman ön planda olmalıyız. Tarayıcı çok uzaktaysa (istenenden 10 kat daha yavaş), o zaman arka planda olmalıyız. Bunun temel bir uygulaması şu şekildedir:

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

Bunu iOS ve Android'in son sürümlerinde test ettim ve öğedeki öznitelikleri ayarlayarak yerel kamerayı açtım <input />.

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

Bu aslında beklediğimden çok daha iyi sonuç veriyor. Bir zamanlayıcının 25 milisaniyede çağrılmasını talep ederek 10 deneme çalıştırır. Ardından, çağırmanın gerçekte ne kadar sürdüğünü ölçer ve 10 denemenin ortalaması 50 milisaniyeden azsa, ön planda olmamız gerektiğini ve kameranın gittiğini varsayarız. 50 milisaniyeden fazlaysa, o zaman hala arka planda olmalı ve beklemeye devam etmeliyiz.

Bazı ek ayrıntılar

Bunun setTimeout()yerine setInterval(), ikincisi, birbirinin hemen ardından çalışan birden çok çağrıyı sıraya koyabildiği için kullandım. Bu, verilerimizdeki gürültüyü büyük ölçüde artırabilir, bu yüzden bunu setTimeout()yapmak biraz daha karmaşık olsa da buna bağlı kaldım.

Bu belirli sayılar benim için iyi çalıştı, ancak en az bir kez kameranın kapatılmasının erken tespit edildiği bir örnek gördüm. Bunun, kameranın açılmasının yavaş olabileceğine ve cihazın arka plana alınmadan önce 10 deneme çalıştırabileceğine inanıyorum. Bu işlevi başlatmadan önce daha fazla deneme eklemek veya 25-50 milisaniye beklemek bunun için bir çözüm olabilir.

Masaüstü

Ne yazık ki, bu gerçekten masaüstü tarayıcılarda işe yaramıyor. Teorik olarak, aynı numara, mevcut sayfayı arka planlı sayfalara göre önceliklendirdiklerinden mümkündür. Ancak birçok masaüstü bilgisayarın arka planda olsa bile sayfanın tam hızda çalışmasını sağlayacak kadar kaynağı vardır, bu nedenle bu strateji pratikte gerçekten işe yaramaz.

Alternatif çözümler

Bir alternatif çözüm, keşfettiğimden pek fazla insan bahsetmediğinden, a FileList. Biz başlamak nulliçinde <input />kullanıcı kamerayı açar ve onlar geri gelmek iptal ederse ve daha sonra nullbir değişiklik olmadığı ve hiçbir olay tetikleyecek olan. Bir çözüm <input />, sayfanın başlangıcına sahte bir dosya atamak olabilir , bu nedenle null, uygun olayı tetikleyecek bir değişiklik olacaktır.

Ne yazık ki, a oluşturmanın resmi bir yolu yoktur FileListve <input />öğe FileListözellikle a gerektirir ve bunun dışında başka bir değeri kabul etmez null. Doğal olarak, FileListnesneler doğrudan inşa edilemez, görünüşe göre artık alakalı olmayan bazı eski güvenlik sorunları için geçerlidir. Bir <input />öğenin dışından birini elde etmenin tek yolu , bir FileListnesne içerebilen bir pano olayını taklit etmek için verileri kopyalayıp yapıştıran bir hack kullanmaktır (temelde bir dosyaya sürükle ve bırak numarası yapıyorsunuz. - web sitesi etkinliğiniz). Bu Firefox'ta mümkündür, ancak iOS Safari için geçerli değildir, bu nedenle benim özel kullanım durumum için uygun değildi.

Tarayıcılar, lütfen ...

Bunun açıkça saçma olduğunu söylemeye gerek yok. Web sayfalarına kritik bir UI öğesinin değiştiğine dair sıfır bildirim verilmesi gerçeği gülünç. Bu, hiçbir zaman tam ekran bir medya yakalama kullanıcı arabirimi için tasarlanmadığı ve teknik olarak "değişiklik" olayını tetiklemediği için spesifikasyondaki bir hatadır .

Ancak , tarayıcı satıcıları bunun gerçekliğini anlayabilir mi? Bu, herhangi bir değişiklik olmadığında bile tetiklenen yeni bir "tamamlandı" olayıyla çözülebilir veya yalnızca "değişikliği" tetikleyebilirsiniz. Evet, bu spesifikasyona aykırı olur, ancak JavaScript tarafında bir değişiklik olayını tekilleştirmek benim için önemsiz, ancak kendi "tamamlandı" olayımı icat etmem temelde imkansız. Tarayıcının durumu hakkında hiçbir garanti sunmuyorsa benim çözümüm bile gerçekten sadece sezgiseldir.

Hali hazırda, bu API temelde mobil cihazlar için kullanılamaz ve bence nispeten basit bir tarayıcı değişikliğinin bunu web geliştiricileri için son derece kolay hale getirebileceğini * sabun kutusundan çıkarlar *.



7

Bir dosya seçip aç / iptal et'i tıkladığınızda, inputöğe odağı yani odağı kaybetmelidir blur. İlk varsayarsak valueait inputboştur, ta istediğiniz olmayan boş değer blurişleyicisi bir Tamam işaret eder ve boş bir değer, bir iptal anlamına gelecektir.

GÜNCELLEME: gizlendiğinde blurtetiklenmez input. Dolayısıyla, geçici olarak .txt dosyasını görüntülemek istemediğiniz sürece, bu numarayı IFRAME tabanlı yüklemelerde kullanamazsınız input.


3
Firefox 10 beta ve Chrome 16'da, blurbir dosya seçtiğinizde tetiklenmez. Diğer tarayıcılarda denemedim.
Blaise

1
Bu işe yaramıyor - girişte bulanıklık, tıklamadan hemen sonra tetikleniyor. Win7 üzerinde Chrome 63.0.3239.84 x64.
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
elseİfadenizin değerlendirilip değerlendirilmediğini kontrol ettiniz mi?
jayarjo

Bu cevabı yazdığım zaman hatırladığım kadarıyla, eğer kullanıcı bir dosya seçer ve sonra dosyayı yeniden seçerse (ama sonra iptal ederse), dosya seçilmemiş olarak değerlendirilecektir, test etmek için sadece selected_file_name değişkeninde alert kullanıyorum. her neyse, o zamandan beri artık test etmedim.
Nicole

4
Bazı çapraz tarayıcı testlerinden sonra, Firefox'un iptal durumunda bir değişiklik olayı tetiklemediğini ve ikinci kez yeniden açılıp iptal tıklandığında seçilen mevcut dosyaları temizlemediğini söyleyebilirim. Tuhaf değil mi?
mix3d

7

Bu çözümlerin çoğu benim için çalışmıyor.

Sorun şu ki, hangi olayın ilk tetikleneceğini asla bilemezsiniz click, öyle changemi yoksa değil mi? Muhtemelen tarayıcının uygulamasına bağlı olduğu için herhangi bir sıra üstlenemezsiniz.

En azından Opera ve Chrome'da (2015'in sonlarında) click, girdileri dosyalarla 'doldurmadan' hemen önce tetiklenir, bu nedenle, daha sonra tetiklenmeyi files.length != 0geciktirene kadar ne kadar süreyi asla bilemezsiniz .clickchange

İşte 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);
    };
});

Sayfayı tıklarsanız ... "İptal tıklandı" ... = | (Chrome ile test edildi)
Eduardo Lucio

5

Sadece tıklama olayını da dinleyin.

Shiboe'nin örneğini takiben, işte bir jQuery örneği:

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

});

Burada eylem halinde görebilirsiniz: http://jsfiddle.net/T3Vwz


1
Chrome 51'de test ettim, dosyaları ilk seçtiğinizde çalışmıyor. Çözüm bir gecikme eklemektir: jsfiddle.net/7jfbmunh
Benoit Blanchon

Kemanınız benim için işe yaramadı - Firefox 58.0.2 kullanılıyor
barrypicker

4

Öncekiyle aynı dosyayı seçerseniz ve bu durumda iptal'i tıklatırsanız iptali yakalayabilirsiniz.

Bunu şu şekilde yapabilirsiniz:

<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 bu yazı bana gerçekten yardımcı oldu.
VPK

4
Tam olarak değil, kullanıcı aynı dosyayı seçerse, bu bir iptal değildir = (
Tiago Peres França

13
onchange yalnızca bir değişiklik varsa ateşler. Dolayısıyla, dosya önceki ile aynı dosyaysa onchange dinleyicisi çalışmayacaktır.
Shane

1
İlk değişikliği tespit ettikten sonra girdi seçimini (input.value = '') temizleyebilirsiniz, böylece kullanıcı aynı dosyayı tekrar seçse bile onchange ikinci kez etkinleşir.
Chris

1
@CodeGems, input.value'yu programlı olarak boş bir dizeye ayarlayabilir. Ancak, başka bir değere ayarlanmasına izin verilmediğinde haklısınız.
Chris

4

En kolay yol, geçici bellekte herhangi bir dosya olup olmadığını kontrol etmektir. Kullanıcı dosya girişini her tıkladığında değişiklik olayını almak istiyorsanız, onu tetikleyebilirsiniz.

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

Shiboe'nun çözümü, mobil webkit üzerinde çalışsaydı iyi olurdu, ama olmadı. Bulabileceğim şey, dosya giriş penceresi açıldığında bazı dom nesnelerine bir mousemove olay dinleyicisi eklemektir, örneğin:

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

Dosya iletişim kutusu açıkken mousemove olayı sayfadan engellenir ve kapatıldığında dosya girişinde herhangi bir dosya olup olmadığını kontrol eder. Benim durumumda, dosya yüklenene kadar işleri engelleyen bir etkinlik göstergesi istiyorum, bu nedenle yalnızca iptal durumunda göstergemi kaldırmak istiyorum.

Ancak hareket edecek fare olmadığından bu durum mobil cihazlar için çözülmez. Benim çözümüm mükemmelden daha az ama bence yeterince iyi.

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

Şimdi aynı dosyaları kontrol etmek için ekranda bir dokunuş dinliyoruz. Bu aktivite göstergesini iptal edip kapattıktan sonra kullanıcının parmağının oldukça hızlı bir şekilde ekrana konacağından oldukça eminim.

Etkinlik göstergesi dosya girdisi değişiklik olayına da eklenebilir, ancak mobil cihazlarda görüntüyü seçme ve değişiklik etkinliğinin tetiklenmesi arasında genellikle birkaç saniyelik bir gecikme olur, bu nedenle etkinlik göstergesinin görüntülenmesi için çok daha iyi UX sürecin başlangıcı.


2

Bu atribute'u buldum, şimdiye kadarki en basit.

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

İşte selectedFilebir input type=file.


Bu benim için iyi bir çözüm, ancak> = 1
Bron Thulke

2

Bunun çok eski bir soru olduğunu biliyorum, ancak birisine yardımcı olması durumunda, iptali tespit etmek için onmousemove olayını kullanırken, kısa bir süre içinde bu tür iki veya daha fazla olayı test etmenin gerekli olduğunu anladım. Bunun nedeni, imleç dosya seçme iletişim penceresinden her dışarı hareket ettirildiğinde ve ana pencereden her dışarı taşındığında ve her defasında tarayıcı (Chrome 65) tarafından tek onmousemove olaylarının oluşturulmasıydı. Fare hareketi olaylarının basit bir sayacı sayacı sıfırlamak için kısa süreli bir zaman aşımı ile birleştiğinde bir tedavi çalıştı.


1
İlk paragraf, daha iyi okunabilirlik için birden fazla paragrafa bölünse bile iyi bir cevaptır. Ancak bu iki "SABUN KUTUSU" satırı, sorular yanıtlara ait olmadığından, onu Yanıtsız olarak sınırlandırıyor. Cevabınızı biraz yeniden ifade edebilir / yeniden ifade edebilir misiniz? Teşekkür ederim.
Filnor

1

Benim durumumda, kullanıcılar resimleri seçerken gönder düğmesini gizlemek zorunda kaldım.

Bu benim çıktığım şey:

$(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-fieldbenim dosya seçicim. Bir kişi üzerine tıkladığında, form gönderme butonunu devre dışı bırakıyorum. Mesele şu ki, dosya diyaloğu kapatıldığında - bir dosya seçmeleri veya iptal etmeleri önemli değil - #image-fieldodağı geri aldım, bu yüzden o olayı dinliyorum.

GÜNCELLEME

Bunun safari ve poltergeist / phantomjs'de çalışmadığını buldum. Uygulamak istiyorsanız bu bilgileri dikkate alın.


1

Shiboe ve alx'in çözümlerini birleştiren en güvenilir koda sahibim:

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

Genellikle, mousemove olayı hile yapar, ancak kullanıcının bir tıklama yapması ve ardından:

  • dosya açma iletişim kutusu kaçış tuşu ile iptal edildi (fareyi hareket ettirmeden), dosya iletişim kutusunu tekrar açmak için başka bir doğru tıklama yaptı ...
  • başka bir uygulamaya odaklandı, tarayıcının dosya açma iletişim kutusuna geri dönüp kapattı, daha sonra enter veya boşluk tuşuyla tekrar açıldı ...

... mousemove olayını almayacağız, dolayısıyla geri aramayı iptal etmeyeceğiz. Ayrıca, kullanıcı ikinci iletişim kutusunu iptal ederse ve bir fareyi hareket ettirirse, 2 iptal geri çağrısı alırız. Neyse ki, özel jQuery focusIn olayı, her iki durumda da belgeye kabarcıklar vererek bu tür durumlardan kaçınmamıza yardımcı olur. Tek sınırlama, birisinin focusIn olayını da engellemesidir.


Ubuntu 16.10'da Chrome 56.0.2924.87'de işletim sistemi iletişim kutusunda dosya seçerken mousemoveolayı yakaladım document. Fareyi hareket ettirmediğinizde veya dosyayı seçmek için sadece klavye kullandığınızda sorun yok. Ben değiştirmek zorunda mousemovetarafından keydownmümkün olduğu kadar erken iptal tespit etmek, ancak "çok erken" iletişim hala açık iken.
Ferdinand Prantl

1

Cevabımın oldukça modası geçmiş olacağını görüyorum, ama asla daha az değil. Ben de aynı problemle karşılaştım. İşte benim çözümüm. Alınan en kullanışlı kod KGA'nın koduydu. Ancak tamamen çalışmıyor ve biraz karmaşık. Ama basitleştirdim.

Ayrıca, asıl sorun yaratan şey, bu 'değişim' olayının odaklandıktan hemen sonra gelmemesi, bu yüzden bir süre beklememiz gerektiğiydi.

"#appendfile" - kullanıcının yeni bir dosya eklemek için tıkladığı dosya. Href'ler odak olaylarını alır.

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

Bunu yalnızca sınırlı durumlarda tespit edebilirsiniz. Özellikle, kromda daha önce bir dosya seçilmişse ve ardından dosya iletişim kutusu tıklanıp iptal tıklandığında, Chrome dosyayı temizler ve onChange olayını çalıştırır.

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

Bu senaryoda, onChange olayını işleyerek ve files özelliğini kontrol ederek bunu saptayabilirsiniz .


1

Aşağıdakiler benim için çalışıyor gibi görünüyor (masaüstünde, pencerelerde):

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

Bu, angularjs $ q kullanır, ancak gerekirse başka bir vaat çerçevesiyle değiştirebilmelisiniz.

IE11, Edge, Chrome, Firefox'ta test edildi, ancak Focus olayını tetiklemediği için bir Android Tablet üzerindeki Chrome'da çalışmıyor gibi görünüyor.


1

fileTipi alan bombalanmasını, olayların bir sürü (bulanıklık güzel olurdu) yanıt vermez. changePek çok insanın odaklı çözümler önerdiğini ve onların oylarının reddedildiğini görüyorum.

changeişe yarıyor, ancak büyük bir kusuru var (ne olmasını istediğimize karşı).

Dosya alanı içeren bir sayfayı yeni yüklediğinizde, kutuyu açın ve iptal düğmesine basın. Hiçbir şey sinir bozucu bir şekilde değişmez .

Yapmayı seçtiğim şey kapılı bir durumda yüklemek.

  • section#after-imageBenim durumumda a formunun bir sonraki kısmı görünmez. Ne zaman file fielddeğişiklikler, bir yükleme düğmesi gösterilir. Başarılı yüklemenin ardından section#after-imagegösterilir.
  • Kullanıcı yükler, dosya iletişim kutusunu açar ve ardından iptal ederse, yükleme düğmesini asla görmez.
  • Kullanıcı bir dosya seçerse, yükleme düğmesi gösterilir. Daha sonra iletişim kutusunu açarlar ve iptal ederlerse, changeolay bu iptal ile tetiklenir ve burada uygun bir dosya seçilene kadar yükleme düğmemi yeniden gizleyebilirim (ve yapabilirim).

Şanslıydım ki bu geçitli durum zaten formumun tasarımıydı. Aynı stili kullanmanıza gerek yoktur, yalnızca yükleme düğmesi başlangıçta gizlenir ve değiştirildiğinde, gönderirken izleyebileceğiniz bir şeye gizli bir alan veya javascript değişkeni ayarlar.

Alanla etkileşim kurulmadan önce [0] dosyaların değerini değiştirmeyi denedim. Bu, değişimle ilgili hiçbir şey yapmadı.

Yani evet, changeen azından alacağımız kadar iyi çalışıyor. Dosya alanı, bariz nedenlerle, ancak iyi niyetli geliştiricilerin hayal kırıklığına uğrayarak güvenceye alınmıştır.

Amacıma uymuyor, ancak onclickbir uyarı istemi yükleyebilir ( alert()sayfa işlemeyi durdurduğu için değil ) ve değişiklik tetiklenirse ve dosyalar [0] boşsa gizleyebilirsiniz. Değişiklik tetiklenmezse, div durumunda kalır.


0

Bunu yapmanın hileli bir yolu vardır (geri aramalar ekleyin veya alert()aramalar yerine bazı ertelenmiş / vaat edilen uygulamaları çözün ):

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

Nasıl çalışır: dosya seçimi iletişim kutusu açıkken, belge fare işaretçisi olaylarını almaz. İletişim kutusunun gerçekten görünmesine ve tarayıcı penceresini engellemesine izin vermek için 1000 ms gecikme vardır. Chrome ve Firefox'ta kontrol edildi (yalnızca Windows).

Ancak elbette bu, iptal edilen diyaloğu tespit etmenin güvenilir bir yolu değildir. Yine de, sizin için bazı UI davranışlarını iyileştirebilir.


En büyük dezavantajlardan biri telefonlarda ve tabletlerde, normal olarak fare kullanmıyorlar!
Peter

0

İşte benim çözümüm, dosya girişi odağını kullanarak (herhangi bir zamanlayıcı kullanmadan)

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


Snippet'inizi çalıştırmak bana veriyorError: { "message": "Uncaught SyntaxError: missing ) after argument list", "filename": "https://stacksnippets.net/js", "lineno": 51, "colno": 1 }
connexo

0

Bu en iyi ihtimalle bilgisayar korsanlığıdır, ancak işte bir kullanıcının bir dosya yükleyip yüklemediğini tespit etmek ve yalnızca bir dosya yüklediyse devam etmelerine izin vermek için benim çözümümün çalışan bir örneğini burada bulabilirsiniz .

Temelde gizlemek Continue, Save, Proceedveya ne olursa olsun düğme. Ardından JavaScript'te dosya adını alırsınız. Dosya adının bir değeri yoksa, Continuedüğmeyi göstermeyin . Bir değeri varsa, düğmeyi gösterin. Bu, ilk başta bir dosya yüklediklerinde ve ardından farklı bir dosya yüklemeye çalıştıklarında ve iptal düğmesine tıkladıklarında da işe yarar.

İşte 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;
}

CSS için bunun çoğu, web sitesini ve düğmeyi herkes için erişilebilir kılmak içindir. Düğmenizi istediğiniz gibi şekillendirin.


0

Bu tam olarak sorunuzu cevaplamıyor. Benim varsayımıma göre, bir senaryonuz var, bir dosya girişi eklediğinizde ve dosya seçimini başlattığınızda ve kullanıcı iptal ederse, girdiyi kaldırırsınız.

Durum böyleyse: Neden boş dosya girişi ekleyelim?

Birini anında oluşturun, ancak yalnızca doldurulduğunda DOM'a ekleyin. Şöyle:

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

Bu yüzden burada anında <input type = "file" /> oluşturuyorum, değişim olayına bağlanıyorum ve hemen tıklamayı çağırıyorum. Değişiklik durumunda, yalnızca kullanıcı bir dosya seçip Tamam'a bastığında tetiklenecektir, aksi takdirde girdi DOM'a eklenmeyecek ve bu nedenle gönderilmeyecektir.

Burada çalışma örneği: https://jsfiddle.net/69g0Lxno/3/


0

// Bulanıklaştırma yerine fareyle üzerine gelme kullanın

var fileInput = $("#fileInput");

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

    //open
} else {

}

0

Zaten JQuery'ye ihtiyacınız varsa, bu çözüm işi yapabilir (bu, benim durumumda gerçekten ihtiyaç duyduğum kodun aynısıdır, ancak bir Söz kullanmak, kodu dosya seçimi çözülene kadar beklemeye zorlamaktır):

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

Bu iş parçacığında önerilen birkaç çözüm vardır ve kullanıcının dosya seçim kutusundaki "İptal" düğmesini tıkladığını tespit etmenin bu zorluğu birçok kişiyi etkileyen bir sorundur.

Gerçek şu ki , kullanıcının dosya seçim kutusundaki "İptal" düğmesine tıklayıp tıklamadığını tespit etmenin% 100 güvenilir bir yolu yoktur . Ancak kullanıcının girdi dosyasına bir dosya ekleyip eklemediğini güvenilir bir şekilde tespit etmenin yolları vardır . Bu, bu cevabın temel stratejisidir!

Bu yanıtı eklemeye karar verdim çünkü görünüşe göre diğer yanıtlar çoğu tarayıcıda çalışmıyor veya mobil cihazlarda garanti edilmiyor.

Kısaca kod 3 noktaya dayanmaktadır:

  1. Girdi dosyası başlangıçta js'de "bellek" içinde dinamik olarak oluşturulur (şu anda "HTML" ye eklemiyoruz);
  2. Dosyayı ekledikten sonra giriş dosyası HTML'ye eklenir, aksi takdirde hiçbir şey olmaz;
  3. Dosyanın kaldırılması, girdi dosyasının HTML'den belirli bir olayla kaldırılmasıyla yapılır; bu, dosyanın "düzenlenmesi" / "değiştirilmesinin" eski girdi dosyası kaldırılarak ve yeni bir dosya oluşturularak yapıldığı anlamına gelir.

Daha iyi anlamak için aşağıdaki koda ve notlara da bakın.

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

}

Teşekkürler! = D


0

Aşağıdaki gibi açısal olarak başardık.

    1. giriş tipi dosyasında bind tıklama olayı.
      1. Odak olayını pencereyle ekleyin ve uploadPanel doğruysa koşul ekleyin, ardından konsolu gösterin.
      2. giriş tipi dosyasına tıklandığında boolean uploadPanel değeri true. ve diyalog kutusu belirir.
      3. iptal VEYA Esc düğmesine tıklandığında, dispanser iletişim kutusu ve konsol belirir.

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

Sadece türü dosya olan girdinize 'değişiklik' dinleyicisi ekleyin. yani

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

JQuery kullanarak yaptım ve tabii ki herkes valina JS'yi kullanabilir (gereksinime göre).

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

            if (this.files.length) {

            alert('file choosen');

            } else {

            alert('file NOT choosen');

            }

    });

.change()kullanıcı dosya seçicide "iptal" i tıklarsa güvenilir şekilde çağrılmaz.
Ben Wheeler

@benWheeler, hem chrome hem de firefox'ta kontrol ettim ve çalıştı ama diğer tarayıcılarda test edilmedi.
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>

Yalnızca kod yanıtları düşük kaliteli olarak kabul edilir: Kodunuzun ne yaptığını ve sorunu nasıl çözdüğünü açıkladığınızdan emin olun. Gönderinize daha fazla bilgi ekleyebilirseniz, soruyu soran kişiye ve gelecekteki okuyuculara yardımcı olacaktır. Tamamen kod tabanlı yanıtları açıklama
Calos

0

Gizli girişli dosya seçimi için çözüm

Not: Bu kod iptali algılamaz, insanların onu tespit etmeye çalıştıkları yaygın bir durumda onu tespit etme ihtiyacını aşmanın bir yolunu sunar.

Buraya gizli bir girdi kullanarak dosya yüklemeleri için bir çözüm ararken geldim, bunun dosya girdisinin iptalini tespit etmenin en yaygın nedeni olduğuna inanıyorum (dosya iletişim kutusunu aç -> bir dosya seçildiyse, bazılarını çalıştırın kod, aksi takdirde hiçbir şey yapmayın), işte benim çözümüm:

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

Kullanım örneği:

Hiçbir dosya seçilmediyse, ilk satırın yalnızca bir kez döneceğini unutmayın selectFile()(veya fileSelectorResolve()başka bir yerden ararsanız).

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

Başka bir örnek:

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

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

Chrome 83.0'da, kullanıcı iptal ettiğinde giriş olayı için geri arama almıyorum ve 2. denemede de değil
Soylent Graham

1
@SoylentGraham Evet, bu kod iptali algılamadığı için, insanların onu tespit etmeye çalıştığı yaygın bir durumda onu tespit etme ihtiyacını aşmanın bir yolunu sunuyor. Bu konuda net olmadığım için bu yorumu cevabıma ekliyorum.
patates

Benim hatam sanırım, cevabınızı "kullanıcının 2. denemede iptal ettiğini söylüyor" şeklinde yanlış okudum
Soylent Graham

-1

Girdi alanında bir jquery değişiklik dinleyicisi oluşturabilir ve alanın değerine göre kullanıcının iptal ettiği veya kapattığı yükleme penceresini tespit edebilirsiniz.

işte bir örnek:

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

Bunun neden olumsuz oy verildiğine dair hiçbir fikrim yok - aradığım buydu. Teşekkür ederim! 🙂
user1274820
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.