Як визначити, коли на введення файлу натискають кнопку "Скасувати"?


112

Як я можу виявити, коли користувач скасовує введення файлу за допомогою вводу html-файлу?

onChange дозволяє мені визначати, коли вони вибирають файл, але я також хотів би знати, коли вони скасовують (закрийте діалогове вікно вибору файлу, не вибираючи нічого).


Ви також можете виявити, чи хтось вибере файл, а потім спробувати вибрати, надіслати його, але скасувати. Ви порожнійe.target.files
jcubic

Відповіді:


45

Хоча це не пряме рішення, а також погано в тому, що воно (лише наскільки я тестував) працює з onfocus (вимагає досить обмежуючого блокування подій), ви можете досягти цього за допомогою наступного:

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

Що приємно в цьому, це те, що ви можете приєднати / відімкнути його в часі за допомогою події файлу, а також здається, що він справно працює з прихованими входами (певна вигода, якщо ви використовуєте візуальне вирішення для шаленого типу вводу за замовчуванням = ' файл '). Після цього потрібно просто розібратися, чи змінилося значення вводу.

Приклад:

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

Дивіться це в дії: http://jsfiddle.net/Shiboe/yuK3r/6/

На жаль, це, здається, працює лише у веб-браузерах. Можливо, хтось ще може розібратися у рішеннях firefox / IE


Я не хотів, щоб фокус події щоразу загорівся, тому я хотів використовувати addEventListener("focus",fn,{once: true}). Однак я не міг змусити його вогонь використати document.body.addEventListener.. Я не знаю чому. Натомість я отримав такий самий результат window.addEventListener.
WoodenKitty

3
Для мене це не працює у Firefox та Edge. Однак, прислухаючись до mousemoveподії на window.document на додаток до прослуховування focusна , windowздається, працює скрізь (по крайней мере в сучасних браузерах, я не дбаю про минуле десятиліття затонулих суден). В основному, будь-яка подія взаємодії може бути використана для цього завдання, яке блокується відкритим діалоговим вікном відкритого файлу.
Джон Вайс

@WoodenKitty, як ви реалізували це за допомогою addEventListener. Я намагаюся зробити це в кутовій та документовій формі. Для мене теж ніхто не працює
Терранс Джексон,

це не працює у прихованих полях введення.
Себастьян

20

Ви не можете.

Результат діалогового вікна файлу не піддається оглядачу.


Чи можна перевірити, чи не відкрито програму вибору файлів?
Ppp

1
@Ppp - Ні. Веб-браузер вам цього не каже.
Oded

6
"Результат діалогового вікна файлу не піддається оглядачу." Це не відповідає дійсності, якщо діалогове вікно закриється вибором файла.
Тревор

3
Зміна викликано з допомогою діалогового вікна файлу піддається браузер. Дивіться, якщо не вибрано жодного файлу і після цього не вибрано жодного файлу, то нічого не змінилося, тому жодна changeподія не розсилається.
Джон Вайс

1
Крім того, якщо файл уже вибрано, і ви вибираєте той самий файл знову, жодна changeподія для цього також не надсилається.
Патрік Робертс

16

Тож я кину капелюх на це питання, оскільки придумав нове рішення. У мене є прогресивна веб-програма, яка дозволяє користувачам робити фотографії та відео та завантажувати їх. Ми використовуємо WebRTC, коли це можливо, але повертаємося до засобу вибору файлів HTML5 для пристроїв з меншою підтримкою * кашель Safari кашель *. Якщо ви спеціально працюєте над мобільним веб-додатком для Android / iOS, який використовує вбудовану камеру для прямого фотографування / фотографії, то це найкраще рішення, з яким я зіткнувся.

Суть цієї проблеми полягає в тому, що коли сторінка завантажується, це fileє null, але тоді, коли користувач відкриває діалогове вікно і натискає "Скасувати", fileце все ще є null, отже, воно не "змінилося", тому жодна подія "зміни" не запускається. Для настільних комп'ютерів це не так вже й погано, оскільки більшість інтерфейсів настільних ПК не залежать від того, щоб знати, коли викликається скасування, але мобільний інтерфейс користувача, який створює камеру для зйомки фото / відео, дуже залежить від того, коли буде натиснуто скасування.

Я спочатку використовував document.body.onfocusподію, щоб виявити, коли користувач повернувся з вибору файлів, і це працювало на більшості пристроїв, але iOS 11.3 зламав його, оскільки ця подія не спрацьовує.

Концепція

Моє рішення для цього - * здригання * для вимірювання часу процесора, щоб визначити, чи стоїть зараз сторінка на передньому плані чи на задньому плані. На мобільних пристроях час обробки відводиться додатку, який зараз на першому плані. Коли видно камеру, вона вкраде час процесора та знизить функцію браузера. Все, що нам потрібно зробити, - це виміряти, скільки часу обробляється наша сторінка, коли камера запустить наш доступний час різко знизиться. Коли камеру закрито (скасовано чи іншим способом), наявний час у нас зменшиться.

Впровадження

Ми можемо виміряти час роботи процесора, використовуючи setTimeout()для виклику зворотного виклику в X мілісекундах, а потім виміряти, скільки часу потрібно, щоб насправді викликати його. Веб-переглядач ніколи не викликає його точно через X мілісекунд, але якщо це розумно закрити, ми повинні бути на першому плані. Якщо веб-переглядач знаходиться дуже далеко (на 10 разів повільніше, ніж потрібно), то ми повинні бути у фоновому режимі. Основна реалізація цього виглядає так:

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

Я тестував це на останній версії iOS та Android, піднімаючи нативну камеру, встановлюючи атрибути на <input />елементі.

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

Це виходить насправді набагато краще, ніж я очікував. Він проводить 10 випробувань, вимагаючи викликати таймер за 25 мілісекунд. Потім він вимірює, скільки часу насправді потрібно для виклику, і якщо в середньому 10 випробувань менше 50 мілісекунд, ми припускаємо, що ми повинні бути на передньому плані, а камера зникла. Якщо вона більша за 50 мілісекунд, то ми все одно повинні знаходитись у фоновому режимі і продовжувати чекати.

Деякі додаткові деталі

Я setTimeout()скоріше використовував, а не setInterval()тому, що останні можуть чергати кілька викликів, які виконуються відразу один за одним. Це може різко збільшити шум у наших даних, тому я затримався, setTimeout()хоча це зробити трохи складніше.

Ці конкретні номери спрацювали для мене добре, хоча я принаймні один раз бачив випадки, коли відключення камери було виявлено передчасно. Я вважаю, що це тому, що камера може повільно відкриватися, і пристрій може провести 10 випробувань, перш ніж вона фактично стане фоновою. Додавання нових випробувань або очікування приблизно 25-50 мілісекунд до запуску цієї функції може бути вирішенням цієї проблеми.

Настільний

На жаль, це не дуже працює для настільних браузерів. Теоретично такий же трюк можливий, коли вони надають пріоритет поточній сторінці перед фоновими сторінками. Однак у багатьох настільних комп'ютерів є достатньо ресурсів, щоб сторінка працювала на повній швидкості навіть у фоновому режимі, тому ця стратегія насправді не працює на практиці.

Альтернативні рішення

Одне альтернативне рішення, яке багато хто згадує, що я досліджувало, насміхається над FileList. Ми починаємо з nullцього <input />пункту, а потім, якщо користувач відкриє камеру і скасує, вони повернуться null, що не є зміною і жодна подія не спровокує. Одним із варіантів рішення було б призначити фіктивний файл для початку <input />сторінки, тому встановлення nullбуде змін, що призведе до відповідної події.

На жаль, немає ніякого способу офіційного способу створити FileList, і <input />елемент вимагає , FileListзокрема , і не буде приймати будь-яке інше значення , крім null. Звичайно, FileListоб'єкти не можуть бути безпосередньо побудовані, як це стосується старої проблеми безпеки, яка, мабуть, вже не актуальна. Єдиний спосіб домогтися одного за межами <input />елемента - це використовувати хак, який копіює вставки даних, щоб підробити подію буфера обміну, яка може містити FileListоб'єкт (ви, в основному, підробляєте файл перетягування та перетягування -подія на веб-сайті). Це можливо в Firefox, але не для iOS Safari, тому це не було життєздатним для мого конкретного випадку використання.

Браузери, будь ласка ...

Потрібно говорити, що це очевидно смішно. Те, що веб-сторінки отримують нульове повідомлення про те, що важливий елемент інтерфейсу змінився, просто сміхотворне. Це справді помилка в специфікації, оскільки вона ніколи не була призначена для повноекранного інтерфейсу медіа-захоплення, а не викликати подію "зміни" технічно є специфікацією.

Однак чи можуть продавці браузерів визнати реальність цього? Це може бути вирішено або за допомогою нової події "зроблено", яка запускається навіть тоді, коли не відбувається жодної зміни, або ви можете просто викликати "зміни" в будь-якому випадку. Так, це було б проти спекуляцій, але для мене тривіально вважати подію зміни на стороні JavaScript, але принципово неможливо винайти мою власну "зроблену" подію. Навіть моє рішення насправді є просто евристикою, якщо немає гарантій щодо стану браузера.

Як відомо, цей API принципово непридатний для мобільних пристроїв, і я думаю, що відносно проста зміна веб-переглядача може зробити це нескінченно простішим для веб-розробників * кроками з мильної коробки *.



7

Коли ви вибираєте файл і натискаєте кнопку "Відкрити / скасувати", inputелемент повинен втратити фокус ака blur. Якщо припустити, що початкове valueзначення inputпорожнє, будь-яке не порожнє значення у вашому blurоброблювальному пристрої означатиме ОК, а порожнє значення означатиме Скасувати.

ОНОВЛЕННЯ: Значок blurне спрацьовує, коли inputприховано. Тому не можна використовувати цей трюк для завантажень на основі IFRAME, якщо ви не бажаєте тимчасово відобразити input.


3
У Firefox 10 бета-версія та Chrome 16 blurне спрацьовує при виборі файлу. Не намагався в інших браузерах.
Блейз

1
Це не працює - розмиття при введенні спрацьовує відразу після клацання. Chrome 63.0.3239.84 x64 на 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
Ви перевірили, чи elseоцінюється ваша заява?
jayarjo

наскільки я пам’ятаю, коли я писав цю відповідь, якщо користувач вибере файл, а потім повторно виділить файл (але потім скасує), він буде розглядатися як не обраний файл, я використовую лише попередження у змінній selected_file_name, щоб перевірити його. у будь-якому разі, з тих пір його більше не тестували.
Rizky

4
Я можу сказати, після деякого перехресного тестування браузера, що Firefox не запускає подію зміни при скасуванні, а також не очищає наявні файли, вибрані при повторному відкритті та відміні натискання вдруге. Дивно, так?
mix3d

7

Більшість цих рішень для мене не працюють.

Проблема в тому, що ти ніколи не знаєш, яка подія спричинить кулак, це clickчи це change? Ви не можете приймати жодне замовлення, оскільки це, мабуть, залежить від реалізації браузера.

Принаймні, в Opera та Chrome (кінець 2015 року) clickспрацьовує безпосередньо перед «заповненням» введення файлами, тож ви ніколи не дізнаєтесь тривалість, files.length != 0доки не відкладете, clickщоб її запустили після change.

Ось код:

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

Якщо ви натиснете на сторінку ... "Скасувати натиснуто" ... = | (Тестовано з Chrome)
Едуардо

5

Просто слухайте також подію натискання.

Наслідуючи приклад Шибо, ось приклад 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!');
    }

});

Ви можете побачити його в дії тут: http://jsfiddle.net/T3Vwz


1
Я протестував на Chrome 51, він не працює під час першого вибору файлів. Рішення полягає в тому, щоб додати затримку: jsfiddle.net/7jfbmunh
Бенуа Бланшон,

Ваша загадка не працювала для мене - використовуючи Firefox 58.0.2
barrypicker

4

Ви можете зловити скасування, якщо ви вибрали той самий файл, що і раніше, і натисніть кнопку Скасувати: у цьому випадку.

Ви можете зробити це так:

<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 ця публікація мені дуже допомогла.
VPK

4
Не точно, якщо користувач вибере той самий файл, це не буде скасувати = (
Tiago Peres França

13
onchange тільки пожежі, якщо відбулися зміни. Отже, якщо файл є тим самим файлом, що і попередній, слухач змінення не запустить.
Шейн

1
Ви можете очистити вибір вхідних даних (input.value = '') після виявлення першої зміни, щоб другий час зміни змінився, навіть якщо користувач знову вибере той самий файл.
Кріс

1
@CodeGems дозволено програмно встановити input.value в порожній рядок. Але ви праві, що встановити його на будь-яке інше значення заборонено.
Кріс

4

Найпростіший спосіб - це перевірити наявність файлів у тимчасовій пам'яті. Якщо ви хочете отримати подію зміни кожного разу, коли користувач натискає на вхід файлу, ви можете його запустити.

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 було б добре, якби він працював на мобільному веб-коді, але це не так. Що я можу придумати - це додати слухача події миші до якогось об'єкта dom під час відкриття вікна введення файлу, наприклад:

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

Подія переходу миші блокується зі сторінки, коли відкрито діалогове вікно файлу, а коли закрите, перевіряє, чи є у файлі вхідні файли. У моєму випадку я хочу, щоб індикатор активності блокував речі до завантаження файлу, тому я хочу лише видалити свій індикатор при скасуванні.

Однак це не вирішує для мобільних пристроїв, оскільки миша не рухається. Моє рішення там менш ніж ідеальне, але я вважаю, що його досить добре.

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

Тепер ми слухаємо дотик на екрані, щоб перевірити ті самі файли. Я майже впевнений, що пальчик користувача буде виставлений на екран досить швидко після скасування та відхилення цього показника активності.

Можна також просто додати індикатор активності у події зміни вводу файлу, але на мобільному пристрої часто відбувається затримка між вибором зображення та зйомкою події зміни, тому його просто набагато краще UX для відображення індикатора активності на початок процесу.


2

Я знайшов цю приналежність, її найпростішу поки що.

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

Ось, selectedFileє input type=file.


Це хороше рішення для мене, за винятком того, що воно повинно бути> = 1
Брон Тулк

2

Я знаю, що це дуже давнє запитання, але на всякий випадок, якщо він комусь допомагає, я виявив, що використовуючи подію onmousemove для виявлення скасування, що потрібно перевірити дві та більше таких подій за короткий проміжок часу. Це сталося через те, що одиничні події onmousemove генеруються браузером (Chrome 65) щоразу, коли курсор виводиться з діалогового вікна вибору файлу і кожного разу переміщується з головного вікна та назад. Простий лічильник подій руху миші в поєднанні з короткою тривалістю таймауту для скидання лічильника до нуля працювало частування.


1
Перший абзац є гарною відповіддю, хоча його можна розділити на декілька для кращого читання. Але ці два рядки "SOAPBOX" роблять його межею для невідповіді, оскільки питання не належать до відповідей. Чи можете ви переробити / перефразувати свою відповідь трохи? Дякую.
Фільнор

1

У моєму випадку мені довелося сховати кнопку подання, коли користувачі вибирали зображення.

Ось що я придумую:

$(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- це мій вибір файлів. Коли хтось натискає на нього, я відключаю кнопку подання форми. Справа в тому, що діалогове вікно файлу закрите - неважливо, вони вибирають файл або скасовують - #image-fieldотримали фокус назад, тому я слухаю цю подію.

ОНОВЛЕННЯ

Я виявив, що це не працює в сафарі та полтергейсті / фантомі. Враховуйте цю інформацію, якщо ви хочете її реалізувати.


1

Поєднуючи рішення Shiboe та Alx, я отримав найнадійніший код:

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

Як правило, подія mousemove робить трюк, але у випадку, якщо користувач здійснив клацання і ніж:

  • скасовано діалогове вікно відкриття файлу клавішею введення (не рухаючи мишкою), зробило ще один точний клік, щоб знову відкрити діалогове вікно файлу ...
  • переключила фокус на будь-яку іншу програму, ніж повернулася до відкритого діалогового вікна файлу браузера та закрила його, ніж знову відкрила за допомогою клавіші enter або пробілу ...

... ми не перейдемо до події миші, а отже, не скасувати зворотний виклик. Крім того, якщо користувач скасує друге діалогове вікно і зробить рух миші, ми отримаємо 2 скасування зворотних викликів. На щастя, спеціальні jQuery focusIn події пускаються до документа в обох випадках, що допомагає нам уникнути подібних ситуацій. Єдине обмеження - якщо один блокує події focusIn.


Я зафіксував mousemoveподію documentпід час вибору файлів у діалоговому вікні ОС Chrome в Chrome 56.0.2924.87 на Ubuntu 16.10. Немає проблем, коли не рухаєте мишу або використовуєте лише клавіатуру для вибору файлу. Мені довелося замінити mousemove, keydownщоб виявити скасування якомога раніше, але не "занадто рано", коли діалогове вікно було ще відкрито.
Фердинанд Прантл

1

Я бачу, що моя відповідь була б досить застарілою, але ніколи не менш. Я зіткнувся з тією ж проблемою. Тож ось моє рішення. Найкорисніший фрагмент коду був KGA. Але це не зовсім працює і є трохи складним. Але я спростив це.

Крім того, головною проблемою був той факт, що подія "зміни" не відбувається миттєво після фокусування, тому нам доведеться почекати деякий час.

"#appendfile" - який користувач натискає, щоб додати новий файл. Hrefs отримують фокус події.

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

Виявити це можна лише за обмежених обставин. Зокрема, у хромі, якщо файл було обрано раніше, а потім натискається діалогове вікно файлу та клацання його скасовується, Chrome очищує файл та запускає подію onChange.

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

У цьому випадку ви можете виявити це, обробляючи подію onChange та перевіряючи властивості файлів .


1

Наступне, здається, працює для мене (на робочому столі, 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;
}

Для цього використовується angularjs $ q, але ви повинні мати можливість замінити його будь-якою іншою системою обіцянок, якщо це необхідно.

Тестовано на IE11, Edge, Chrome, Firefox, але, схоже, він не працює на Chrome на планшетному ПК Android, оскільки він не викликає події Focus.


1

Поле fileтипу, розчаровує, не реагує на багато подій (розмиття було б чудовим). Я бачу дуже багато людей, які пропонують changeорієнтовані рішення, і їм стає недоцільним.

changeдійсно працює, але він має головний недолік (проти того, що ми хочемо, щоб це сталося).

Коли ви щойно завантажуєте сторінку, що містить поле файлу, відкрийте поле та натисніть Скасувати. Ніщо, розчаровує, не змінюється .

Що я вирішив зробити - це завантаження в закритому стані.

  • Наступна частина форми a section#after-imageв моєму випадку прихована від перегляду. Коли я file fieldзміниться, відображається кнопка завантаження. Після успішного завантаження section#after-imageвідображається.
  • Якщо користувач завантажується, відкриває діалогове вікно файлів, а потім скасовує, вони ніколи не бачать кнопку завантаження.
  • Якщо користувач вибирає файл, відображається кнопка завантаження. Якщо вони потім відкриють діалогове вікно і скасують, changeподія викликається цим скасуванням, і там я можу (і зробити) повторно приховати кнопку завантаження, поки не буде обраний належний файл.

Мені пощастило, що ця держава із закритим типом вже була дизайном моєї форми. Вам не потрібно використовувати один і той же стиль, просто натискаючи кнопку завантаження спочатку прихованою та після зміни, встановивши приховане поле чи змінну javascript на те, що ви можете контролювати при поданні.

Я спробував змінити значення файлів [0], перш ніж поле було взаємодіяне з. Це не зробило нічого щодо зміни.

Так що так, changeпрацює, принаймні так добре, як ми збираємося отримати. Файлове поле захищене, з очевидних причин, але на розчарування добровільних розробників.

Це не відповідає моєму призначенню, але ви, можливо, зможете onclickзавантажити завантажувальний запит (не alert(), тому що це зупиняє обробку сторінки) і приховати її, якщо ініціюється зміна, а файли [0] недійсні. Якщо зміни не спрацьовують, div залишається у своєму стані.


0

Існує шалений спосіб зробити це (додати зворотні дзвінки або вирішити якусь відстрочену / обіцяючу реалізацію замість alert()викликів):

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

Як це працює: поки відкрито діалогове вікно вибору файлів, документ не отримує подій вказівника миші. Існує затримка на 1000 мс, щоб діалог дійсно з’явився та блокував вікно браузера. Перевірено в Chrome та Firefox (лише для Windows).

Але, звичайно, це не надійний спосіб виявити скасований діалог. Хоча це може покращити поведінку інтерфейсу для вас.


Основним недоліком є ​​телефони та планшети, вони не використовують мишей нормально!
Петро,

0

Ось моє рішення, використовуючи фокус введення файлу (не використовуючи жодних таймерів)

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

0

Це в кращому випадку хакі, але ось робочий приклад мого рішення щодо виявлення того, завантажив чи ні користувач файл, і дозволяти їм продовжуватись лише тоді, коли він завантажив файл.

В основному приховати Continue, Save, Proceedабо що - то ваша кнопка. Потім у JavaScript ви захопите ім'я файлу. Якщо ім'я файлу не має значення, не показуйте Continueкнопку. Якщо у нього є значення, покажіть кнопку. Це також працює, якщо вони спочатку завантажують файл, а потім намагаються завантажити інший файл і натисніть Скасувати.

Ось код.

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 дуже багато цього - зробити веб-сайт та кнопку доступними для всіх. Стилюйте свою кнопку до всього, що вам подобається.


0

Ну, це точно не відповідає на ваше запитання. Моє припущення полягає в тому, що у вас є сценарій, коли ви додаєте введення файлу і викликаєте вибір файлу, і якщо користувачі звертаються до скасувати, ви просто видаліть введення.

Якщо це так, то: Навіщо додавати порожній файл?

Створіть його на льоту, але додайте його до DOM лише тоді, коли він заповнений.

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

Тому тут я створюю <input type = "file" /> на льоту, прив'язуюсь до події зміни, а потім негайно викликаю клацання. При зміні буде запущено лише тоді, коли користувач вибере файл і натисне Ок, інакше введення не буде додано до DOM, тому не буде подано.

Приклад роботи тут: https://jsfiddle.net/69g0Lxno/3/


0

// Використовуйте курсор замість розмиття

var fileInput = $("#fileInput");

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

    //open
} else {

}

0

Якщо вам вже потрібна JQuery, це рішення може зробити свою роботу (це саме той самий код, який я насправді потрібен у моєму випадку, хоча використання Обіцяння просто для того, щоб змусити код чекати, поки вибір файлу не буде вирішений):

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

У цій темі є кілька запропонованих рішень, і ця складність у виявленні, коли користувач натискає кнопку "Скасувати" у вікні вибору файлів, є проблемою, яка зачіпає багатьох людей.

Справа в тому, що немає 100% надійного способу виявити, чи користувач натиснув кнопку "Скасувати" у вікні вибору файлів. Але є способи надійного виявлення, чи додав користувач файл до вхідного файлу. Отже, це основна стратегія цієї відповіді!

Я вирішив додати цю відповідь, оскільки, мабуть, інші відповіді не працюють у більшості браузерів або гарантуються на мобільних пристроях.

Коротко код заснований на 3 балах:

  1. Вхідний файл спочатку динамічно створюється в "пам'яті" в js (ми не додаємо його до "HTML" на даний момент);
  2. Після додавання файла вхідний файл додається до HTML, інакше нічого не відбувається;
  3. Видалення файлу відбувається за допомогою видалення вхідного файлу з HTML конкретною подією, а це означає, що "редагування" / "модифікація" файлу відбувається шляхом видалення старого вхідного файлу та створення нового.

Для кращого розуміння дивіться код нижче та примітки.

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

}

Дякую! = D


0

Ми досягли такого, як нижче.

    1. прив'язати подію клацання у файлі типу введення.
      1. Приєднайте подію фокусування за допомогою вікна та додайте умову, якщо uploadPanel вірно, тоді покажіть консоль.
      2. при натисканні на файл типу введення значення boolean uploadPanel є істинним. і з'явиться діалогове вікно.
      3. при скасуванні АБО кнопку Esc натисніть, тоді з'явиться диспансер і консоль діалогового вікна.

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

Просто додайте слухача "змінити" на вхід, тип якого - файл. тобто

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

Я зробив за допомогою jQuery і, очевидно, кожен може використовувати валіну JS (відповідно до вимоги).

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

            if (this.files.length) {

            alert('file choosen');

            } else {

            alert('file NOT choosen');

            }

    });

.change()не надійно викликається, якщо користувач натискає "скасувати" на засобі вибору файлів.
Бен Вілер

@benWheeler, я перевірив це як на chrome, так і на firefox, і він працював моїм, але не перевіреним на інших браузерах.
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>

Відповіді, що стосуються лише коду, вважаються низькою якістю: обов’язково надайте пояснення, що робить ваш код і як він вирішує проблему. Це допоможе запитувачу та майбутнім читачам, якщо ви зможете додати більше інформації у своєму дописі. Див. Пояснення відповідей на основі коду
Калос

0

Рішення для вибору файлів із прихованим входом

Примітка: цей код не виявляє скасування, він пропонує спосіб обійти необхідність його виявлення у звичайному випадку, коли люди намагаються його виявити.

Я потрапив сюди, шукаючи рішення для завантаження файлів за допомогою прихованого вводу, я вважаю, що це найпоширеніша причина шукати спосіб виявлення скасування введення файлу (відкрити діалогове вікно файлу -> якщо файл був обраний, тоді запустіть деякий код, інакше нічого не робити), ось моє рішення:

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

Приклад використання:

Зауважте, що якщо не було обрано жодного файлу, то перший рядок повертається лише один раз, коли selectFile()він знову викликається (або якщо ви дзвонили fileSelectorResolve()з іншого місця).

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

Ще один приклад:

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

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

У chrome 83.0 я не отримую зворотного виклику для події введення, коли користувач скасовує, а також не для другої спроби
Soylent Graham

1
@SoylentGraham Так, оскільки цей код не виявляє скасування, він пропонує спосіб обійти необхідність виявити його у звичайному випадку, коли люди намагаються його виявити. Оскільки мені це було не зрозуміло, я додаю цей коментар до своєї відповіді.
картопля

Дуже погано, я думаю, я неправильно прочитав вашу відповідь, оскільки "це повідомляє вам, що користувач скасований у 2-й спробі"
Soylent Graham

-1

Ви можете зробити слухача зміни jquery у полі введення та виявити, що користувач скасує або закриє вікно завантаження за значенням поля.

ось приклад:

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

Не маю ідеї, чому це заперечується - це те, що я шукав. Дякую! 🙂
користувач1274820
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.