Вопрос или проблема
У меня есть функция, которая запрашивает у пользователя файл и возвращает файл, если он выбран, и (должна) возвращать null, если ничего не выбрано.
async pickFile(allowed_extensions?: string[]): Promise<File | null> {
const input = document.createElement('input');
input.type="file";
input.accept = allowed_extensions?.join(',') ?? '*';
input.click();
return new Promise((resolve) => {
input.onchange = () => {
if (input.files && input.files.length > 0) {
resolve(input.files[0]);
} else {
resolve(null);
}
};
});
}
Если я выбираю файл и подтверждаю, это работает нормально. Однако если я закрываю окно выбора, кажется, что событие onchange не срабатывает. Ожидается ли такое поведение? Если да, то как это можно обработать?
onchange
не срабатывает при закрытии окна.
Вы должны добавить еще один обработчик событий для события cancel
, которое сработает, когда вы закроете всплывающее окно:
function pickFile() {
const input = document.createElement('input');
input.type="file";
input.click();
return new Promise((resolve) => {
input.onchange = () => {
console.log('событие onchange сработало');
if (input.files && input.files.length > 0) {
resolve(input.files[0]);
} else {
resolve(null);
}
};
input.addEventListener("cancel", () => {
console.log('Ввод отменен');
resolve(null)
});
});
}
<button type="button" onclick="pickFile()">Нажми на меня!</button>
.
Ответ или решение
Вопрос, который вы подняли, касается поведения события onchange
в контексте элемента input
типа "file" и его взаимодействия с пользователем. Давайте разберёмся подробнее, почему событие onchange
не срабатывает, когда пользователь закрывает окно выбора файла, и как можно решить эту проблему.
Понимание Проблемы
Событие onchange
у элемента input
с типом "file" срабатывает только при фактическом выборе файла. Если пользователь закрывает диалоговое окно без выбора, это поведение обусловлено стандартами веб-браузеров, так как в этом случае не происходит изменения состояния элемента. Таким образом, событие onchange
не вызывается, и ваш код не получает возможности обработать отмену выбора файлов.
Решение Проблемы
Как упоминалось в вашем вопросе, для обработки подобной ситуации вам нужно добавить другой слушатель событий. Однако в данном контексте событие cancel
, к сожалению, не поддерживается для элемента input
. Вместо этого можно использовать метод close
или же продумать обходные пути для обработки бездействия пользователя.
Пример с добавлением обработчика
Можно предложить более удобный подход к этой задаче. Поскольку события cancel
не существует, единственным способом обработки отмены выбора будет наблюдение за тем, закрывает ли пользователь диалог и не выбирает ли файл. Ты можешь реализовать это, используя таймер на определение того, был ли возвращён фокус на страницу после клика.
Вот пример, как можно реализовать этот подход:
async function pickFile(allowed_extensions = []) {
const input = document.createElement('input');
input.type = "file";
input.accept = allowed_extensions.join(',') || '*';
input.style.display = 'none'; // Скрываем элемент
document.body.appendChild(input);
input.click();
return new Promise((resolve) => {
input.onchange = () => {
if (input.files && input.files.length > 0) {
resolve(input.files[0]);
} else {
resolve(null);
}
};
let timeout = setTimeout(() => {
if (!input.files || input.files.length === 0) {
resolve(null);
}
}, 1000); // Тайм-аут, чтобы ждать, отменил ли пользователь
input.addEventListener('blur', () => {
clearTimeout(timeout); // Очищаем таймер, если фокус уходит
resolve(null);
});
}).finally(() => {
document.body.removeChild(input); // Удаляем элемент после завершения
});
}
Заключение
Ваша изначальная проблема связана с поведением браузера, которое не триггерит событие onchange
, когда пользователь просто закрывает окно выбора. Однако возможно реализовать альтернативные методы отслеживания фокуса и тайм-аут, чтобы обрабатывать такие ситуации.
Изучив данное решение, вы сможете более эффективно управлять поведением вашего приложения и предоставлять пользователям более интуитивный интерфейс. Надеюсь, это ответило на ваш вопрос и помогло найти решение. Если у вас есть дополнительные вопросы или необходимые уточнения, не стесняйтесь задать их!