Программно сгенерированный/активированный входной файл не всегда запускает событие "вход"

У меня есть кнопка в моем веб-приложении, которая имеет следующий код в обработчике событий click:

const fileInputEl = document.createElement('input');
fileInputEl.type = 'file';
fileInputEl.accept = 'image/*';

fileInputEl.addEventListener('input', (e) => {
  if (!e.target.files.length) {
    return;
  }

  // Handle files here...
});  

fileInputEl.dispatchEvent(new MouseEvent('click'));

Иногда (около 1 из 8) после выбора файла событие input не срабатывает после выбора файла. Я предполагаю, что это ошибка браузера вокруг жизненного цикла элемента.

Каким-то образом это не добавляет элемент на страницу и удаляет его позже? Какой правильный способ справиться с этим в современных браузерах в наши дни?

Я тестирую Google Chrome в Windows.

JSFiddle: http://jsfiddle.net/pja1d5om/2/

Ответ 1

Похоже, что это ошибка браузера/случайность и, вероятно, что-то связано с сборкой мусора. Я могу обойти это, добавив файл ввода в документ:

fileInputEl.style.display = 'none';
document.querySelector('body').appendChild(fileInputEl);

Когда это будет сделано, его можно очистить с помощью:

fileInputEl.remove();

Ответ 2

Цитата из вашего вопроса: иногда (около 1 из 8), после выбора файла, событие ввода не срабатывает после выбора файла.

Я могу подтвердить это поведение с помощью input и с событиями change используя Opera (версия 55.0.2994.61, новейшая версия на данный момент), которая использует механизм браузера Google Chrome "Blink". Это примерно 1 из 25.

Решение

Это происходит потому, что иногда ваш объект элемента ввода удалялся после закрытия диалогового окна файла, поскольку он больше не используется. И когда это произойдет, у вас нет цели, которая могла бы получить событие input или change.

Чтобы решить эту проблему, просто добавьте свой элемент ввода где-нибудь в DOM после создания в качестве скрытого объекта, например:

fileInputEl.style.display = 'none';
document.body.appendChild(fileInputEl);

И затем, когда событие было уволено, вы можете удалить его, как показано ниже:

document.body.removeChild(fileInputEl);

Полный пример

function selectFile()
{
    var fileInputEl = document.createElement('input');
    fileInputEl.type = 'file';
    fileInputEl.accept = 'image/*';
    //on this way you can see how many files you select (is for test only):
    fileInputEl.multiple = 'multiple';

    fileInputEl.style.display = 'none';
    document.body.appendChild(fileInputEl);

    fileInputEl.addEventListener('input', function(e)
    {
        // Handle files here...
        console.log('You have selected ' + fileInputEl.files.length + ' file(s).');
        document.body.removeChild(fileInputEl);
    });  

    try
    {
        fileInputEl.dispatchEvent(new MouseEvent('click'));
    }
    catch(e)
    {
        console.log('Mouse Event error:\n' + e.message);
        // TODO:
        //Creating and firing synthetic events in IE/MS Edge:
        //https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/dn905219(v=vs.85)
    }
}
<input type="button" onclick="selectFile()" value="Select file">

Ответ 3

Это очень интересная ошибка, которую вы получили там, я не смог ее воспроизвести.

Есть ли причина, по которой вы приближаетесь к вводу файла так, как вы делаете? Это потому, что вы хотите попробовать и стилизовать его?

Я прочитал эту статью, и я применил то, что она делала. Я обнаружил, что это сработало хорошо. То, что эта статья пытается сделать, - это связать ярлык со входом с атрибутом для тега метки. Затем в CSS и JavaScript тег ввода файла скрыт, и ярлык действует как "кнопка" по существу.

Например...

Примечание. Я внес некоторые изменения в код, но все они относятся к Освальдасу Валутису, который является автором статьи, упомянутой выше в CoDrops.

var inputs = document.querySelectorAll('.inputfile');

inputs.forEach(input => {

  var label = input.nextElementSibling,
    labelVal = label.innerHTML;

  input.addEventListener('change', function(e) {

    var fileName = '';

    if (this.files && this.files.length > 1)
      fileName = (this.getAttribute('data-multiple-caption') || '').replace('{count}', this.files.length);
    else
      fileName = e.target.value.split('\\').pop();

    if (fileName)
      label.querySelector('span').innerHTML = fileName;
    else
      label.innerHTML = labelVal;

  });

});
* {
  font-family: sans-serif;
  font-weight: 300;
}

.inputfile {
  display: none;
}

.inputfile+label {
  font-size: 1.25em;
  font-weight: 700;
  color: white;
  background-color: darkred;
  display: inline-block;
  padding: 10px;
  border-radius: 10px;
  border: 1px darkred solid;
  cursor: pointer;
}

.inputfile+label:hover {
  background-color: darkred;
}
<input type="file" name="file" id="file" class="inputfile" data-multiple-caption="{count} files selected" multiple />
<label for="file">Choose a file <span></span></label>