Both examples work in Chrome and Opera, but fail in Firefox 56.0.
I want to set the files FileList of a form's file input.[Codepen]
HTML
<form>
<input type="file" id="input1" multiple>
<br>
<input type="file" id="input2" multiple>
</form>
JAVASCRIPT
var input1 = document.getElementById("input1");
var input2 = document.getElementById("input2");
input1.onchange = function () {
console.log(input1.files);
input2.files = input1.files;
};
On Chrome and Opera, selecting files in the first input, will also change the second. In Firefox, the second input doesn't change, even though the filelist appears to be correct in the console's output.
The overall goal is to create a drag-drop upload interface.
Prototype here.
The ability to set a FileList as the input.files property programmatically has been added as an PR to the specs three months ago, even if webkit allow this for years. Firefox has landed a patch in its next stable version, 57 and Edge is probably still working on it (I don't have an account to see the progress).It seems it has now landed in Edge too.
The main use case for this feature is to allow DataTransfer.files from e.g a drag&drop event or a paste one to be added to an <input> field. As such, only a FileList is allowed (and null to clear the input).
So in the case exposed in the body of your question, I don't really see the point of using this feature between two <input> fields.
If you want to keep in memory selected FileList, you can always convert it as an Array of files.
If you want to be able to move your filled input in a <form> later on, you can do it directly with the inputElement and DOM methods.
And if you need to workaround the limitations this new feature leverages, you can always fill an FormData with the DataTransfer's files and send this FormData through xhr instead of using the default HTML form method.
And Since I first missed the real use-case, in the codepen, here is an possible implementation to workaround the drag&drop issue you are facing, even on older browsers that didn't support this new feature.
This uses an hidden input in the dropZone, which will catch the dropped files directly.
// called when the input hidden in the dropZone changes
function handleDroppedChange(evt) {
this.removeEventListener('drop', handleDroppedChange); // only once
// create a new hidden input
var clone = this.cloneNode();
clone.addEventListener('change', handleDroppedChange);
clone.addEventListener('change', handleBasicChange);
this.parentNode.insertBefore(clone, this);
// replace the visible one with the current hidden one
var form = document.querySelector('form');
var previous = form.querySelector('input[type=file]');
form.insertBefore(this, previous);
form.removeChild(previous);
this.id = previous.id; // for the <label>
}
// add first listeners
var hiddenTarget = dropzone.querySelector('input[type="file"]');
hiddenTarget.addEventListener('change', handleDroppedChange);
hiddenTarget.addEventListener('change', handleBasicChange);
file_input.addEventListener('change', handleBasicChange);
// handle drop over enter leave as usual on the parent
dropzone.ondragover = dropzone.ondragenter = function(evt) {
evt.preventDefault();
dropzone.className = "drag";
};
dropzone.ondragleave = function(evt) {
evt.preventDefault();
dropzone.className = "";
};
dropzone.ondrop = function(evt) {
dropzone.className = "";
console.log("drop");
};
// will trigger for any kind of changes (dropped or manual)
function handleBasicChange(evt) {
var file_names = Array.prototype.map.call(this.files, function(f){return f.name;});
label.innerHTML = "Changed " + file_names.join('<br>');
// start upload process
};
#dropzone {
display: inline-block;
padding: 25px;
border: 8px dashed #b11;
position: relative;
}
#dropzone.drag {
border-color: #f74;
}
#dropzone>input{
opacity: 0;
position: absolute;
height: 100%;
width: 100%;
left: 0;
top: 0;
/* below rules avoid clicks on hidden input */
pointer-events: none;
}
#dropzone.drag>input{
pointer-events: all;
}
<form>
<input type="file" id="file_input" multiple>
</form><br><br>
<div id="dropzone">
<label id="label" for="file_input">Drop here.</label>
<!-- we use an hidden file input to catch the dropped files -->
<input type="file" multiple>
</div>
Related
This question already has answers here:
info on javascript document.createElement()
(4 answers)
Closed 12 months ago.
I want to display a new input field with a new name everytime a button is clicked. how do I achieve that? I found solutions to adding the input but none mentions adding a new name to it.
HTML:
Add a keyword
<div id="fieldContainer"></div>
Javascript:
function addKeywordFields() {
var mydiv = document.getElementById("fieldContainer");
mydiv.appendChild(document.createTextNode("<input type='text' name=''>"))
}
You should not use document.createTextNode to create an input. This will only create a text element with content specified inside it.
Instead you should create an input using document.createElement('input') and specify its type and name.
Since you need a dynamic name, you have to integrate a dynamic name generation logic. Here I used (new Date()).toISOString() since this will e unique each time. Instead you have to use your own logic.
Working Fiddle
function addKeywordFields() {
var mydiv = document.getElementById("fieldContainer");
const input = document.createElement('input');
input.type = 'text';
input.name = (new Date()).toISOString(); // Some dynamic name logic
mydiv.appendChild(input);
}
<a onclick="addKeywordFields()">Add a keyword</a>
<div id="fieldContainer"></div>
There's a ton of DOM methods designed to create, destroy, move, etc. There's generally three ways to create a DOM element programmatically:
clone an element with .cloneNode() and .append()
parse a string that represents HTML (aka htmlString) use .insertAdjacentHTML() instead of .innerHTML
create an element with .createElement() and .append()
Assigning attributes/properties to an element can be done initially with .setAttribute() once an attribute has been assigned to an element, it is technically a property. This distinction between attribute and property is blurry because methods seem to work 100% as does assigning properties. jQuery is not so flexible in this instance, .attr() and .prop() have silently failed on me when I forget about attribute/property quirks.
I rarely use methods to assign attributes, properties are terse and simple to use, here's a few common ones:
obj.id = "ID", obj.className = "CLASS", obj.name = "NAME"❉,
obj.type = "TEXT", obj.dataset.* = "*"
❉This is the property you use to set/get name attribute
I always add a <form> if there's multiple form controls. There are special features and terse syntax if you use interfaces like HTMLFormElement✤ and HTMLFormControlsCollection✼. Moreover having a <form> you can use it to listen for events for everything within it❉ and also take advantage of special form events like "input", "change", "submit", etc.
HTMLFormElement.elements collects all form controls (listed below)
<button>, <fieldset>, <input>, <object>, <output>, <select>, <textarea>.
From the .elements property, any form control can be referenced by #id or [name]. If there is a group of form controls that share a [name] they can be collected into a HTMLCollection.
References
HTMLFormElement✤
HTMLFormControlsCollection✼
Events
Event Delegation❉
Further details are commented in example below
// Reference the form (see HTMLFormElement)
const UI = document.forms.UI;
// Register form to click event
UI.addEventListener('click', makeInput);
// Define a counter outside of function
let dataID = 0;
// Pass Event Object
function makeInput(e) {
/*
"this" is the form
.elements property is a collection of all form controls
(see HTMLFormsCollection)
*/
const io = this.elements;
// Event.target points to the tag user clicked
const clk = e.target;
/*
This is the fieldset containing the inputs it's in bracket notation
instead of dot notation because it's a hyphenated property
*/
const grp = io['form-group'];
// This is the static input
const inp = io.data;
// if the clicked tag is a button...
if (clk.matches('button')) {
// ...increment the counter
dataID++;
/*
This switch() delegates what to do by the clicked button's [name].
Each case is a different way to create an element and assign a unique
[name] by concatenating the "data" with the current number of dataID
*/
switch (clk.name) {
case 'clone':
const copy = inp.cloneNode(true);
copy.name = 'data' + dataID;
grp.append(copy);
break;
case 'html':
const htmlStr = `
<input name="data${dataID}">`;
grp.insertAdjacentHTML('beforeEnd', htmlStr);
break;
case 'create':
const tag = document.createElement('input');
tag.name = 'data' + dataID;
grp.append(tag);
break;
default:
break;
}
}
};
form {
display: flex;
}
fieldset {
display: inline-flex;
flex-direction: column;
justify-content: flex-startd;
max-width: 90%;
}
input,
button {
display: inline-block;
width: 80px;
margin-bottom: 4px;
}
input {
width: 150px;
}
button {
cursor: pointer;
}
<form id='UI'>
<fieldset name='btn-group'>
<button name='clone' type='button'>Clone Node</button>
<button name='html' type='button'>Parse htnlString</button>
<button name='create' type='button'>Create Element</button>
</fieldset>
<fieldset name='form-group'>
<input name='data'>
</fieldset>
</form>
In the following example, when you click on the label, the input changes state.
document.querySelector("label").addEventListener("click", function() {
console.log("clicked label");
});
label {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
<input type="checkbox" id="1">
<label for="1">Label</label>
In Chrome, when you move the cursor between the mousedown and mouseup events the input still gets triggered, whereas in Firefox the checkbox doesn't change state.
Is there a way to fix this? (without using JavaScript event listeners)
Firefox version: 69.0.3 (64-bit)
Full set of actions when using chrome.
Press the button over the label
Move the cursor around (even outside the label) while still holding the button
Return the cursor back to the label
Release the button
Introduction
Although I specifically stated in the question that the answer shouldn't involve JavaScript, all the answers worked with JavaScript.
Since this seems to be a Firefox bug and most of the answers submitted at this point would require me to also alter the rest of my code, I decided to create a script that can be run once, will deal with all the labels regardless of when they are added to the dom and will have the least impact on my other scripts.
Solution - Example
var mutationConfiguration = {
attributes: true,
childList: true
};
if (document.readyState === "complete") onLoad();
else addEventListener("load", onLoad);
var managingDoms = [];
function onLoad() {
document.querySelectorAll("label[for]").forEach(manageLabel);
if (typeof MutationObserver === "function") {
var observer = new MutationObserver(function(list) {
list.forEach(function(item) {
({
"attributes": function() {
if (!(item.target instanceof HTMLLabelElement)) return;
if (item.attributeName === "for") manageLabel(item.target);
},
"childList": function() {
item.addedNodes.forEach(function(newNode) {
if (!(newNode instanceof HTMLLabelElement)) return;
if (newNode.hasAttribute("for")) manageLabel(newNode);
});
}
}[item.type])();
});
});
observer.observe(document.body, mutationConfiguration);
}
}
function manageLabel(label) {
if (managingDoms.includes(label)) return;
label.addEventListener("click", onLabelClick);
managingDoms.push(label);
}
function onLabelClick(event) {
if (event.defaultPrevented) return;
var id = this.getAttribute("for");
var target = document.getElementById(id);
if (target !== null) {
this.removeAttribute("for");
var self = this;
target.click();
target.focus();
setTimeout(function() {
self.setAttribute("for", id);
}, 0);
}
}
label {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
padding: 10px;
border: 1px solid black;
cursor: pointer;
}
<input type="checkbox" id="a">
<input type="text" id="b">
<label for="a">A</label>
<script>
setTimeout(function() {
var label = document.createElement("label");
label.setAttribute("for", "b");
label.textContent = "b";
document.body.appendChild(label);
}, 3E3);
</script>
Explanation
onLabelClick
The function onLabelClick needs to get called whenever a label gets clicked, it will check if the label has a corresponding input element. If it does, it will trigger it, remove the for attribute of the label so that the browsers don't have the bug won't re-trigger it and then use a setTimeout of 0ms to add the for attribute back once the event has bubbled up. This means event.preventDefault doesn't have to get called and thus no other actions/events will get cancelled. Also if I need to override this function I just have to add an event-listener that calls Event#preventDefault or removes the for attribute.
manageLabel
The function manageLabel accepts a label checks if it has already been added an event listener to avoid re-adding it, adds the listener if it hasn't already been added, and adds it to the list of labels have been managed.
onLoad
The function onLoad needs to get called when the page gets loaded so that the function manageLabel can be called for all the labels on the DOM at that moment. The function also uses a MutationObserver to catch any labels that get added, after the load has been fired (and the script has been run).
The code displayed above was optimized by Martin Barker.
I know you did not want JS Event listeners, but im thinking you mean to identify the movement this does not but is using mousedown instead of click (mousedown followed by mouseup).
While this is a known bug in Firefox you could get around it by using the mousedown event
I have had to change your id to be a valid one id's must start with a character
document.querySelector("label").addEventListener("mousedown", function(evt) {
console.log("clicked label");
// if you want to to check the checkbox when it happens,
let elmId = evt.target.getAttribute("for")
let oldState = document.querySelector("#"+elmId).checked;
setTimeout(() => {
if(oldState == document.querySelector("#"+elmId).checked){
document.querySelector("#"+elmId).checked = !oldState;
}
}, 150)
});
label {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
<input type="checkbox" id="valid_1">
<label for="valid_1">Label</label>
No. This looks like a firefox bug and not an issue with your code. I don't believe there is a css workaround for this behavior.
You may be able to report it to Mozilla and get the issue fixed, but I wouldn't rely on that. https://bugzilla.mozilla.org/home
For a potential workaround I would suggested triggering the event on mouseup instead.
Without javascript, when you click the label that has its "for" value the same as an inputs "id" value then the input gets clicked, but this is not consistent amongst browsers.
If a browser does follow the above, then your javascript click event will cancel out the effect, which ends up doing nothing.
A solution
To have consistency amongst browsers you could adopt a different strategy:
Onload dynamically change all the attribute 'for' for 'data-for', so it nulls the original browser affect. Then you can apply your click event to each label.
var replaceLabelFor = function () {
var $labels = document.querySelectorAll('label');
var arrLabels = Array.prototype.slice.call($labels);
arrLabels.forEach(function (item) {
var att = document.createAttribute('data-for');
att.value = String(this.for);
item.setAttributeNode(att);
item.removeAttribute('for')
});
}
var applyMyLabelClick() {
document.querySelector("label").addEventListener("click", function() {
console.log("clicked label");
});
}
// x-browser handle onload
document.attachEvent("onreadystatechange", function(){
if(document.readyState === "complete"){
document.detachEvent("onreadystatechange", arguments.callee);
replaceLabelFor();
applyMyLabelClick();
}
});
Attaching the event to the document and targeting the element you require in there ought to sort this issue.
$(document).on('click', '.item', function(event) {});
From reading on this topic in the past, it’s down to Firefox understanding your action as an attempt to drag the element however, since user select is none, it just prevents the default behaviour.
This is based on fairly limited knowledge but it seems to be a known bug/quirk and there is a few articles floating around supporting this.
I am building a BackboneJS/Marionette App and currently trying to allow users to upload multiple files.
This works when they select multiple files at the same time but I would like to have the functionality to allow them to select 1 file and then click the input again and add to the original FileList Object if possible.
Or I would like to find a way to allow my save function to grab the files from multiple inputs if need be.
I am open to any and all suggestions
This is the HTML5 File API code I am using and I am using the jquery/js offered by the MDN HTML5 File API guide
<input id="uploads" name="uploads" type="file" class="file uploads file1" multiple style="display: none"/>
Select File 1<br>
A FileList object from an HTMLInputElement (which is the underlying object that will hold the Files in your input.files) can only be modified in order to clear it completely (with input.value = null).
There are now ways to actually circumvent this, introduced by the DataTransfer constructor, but as of today, only Chrome and latest Firefox do support this constructor.
So the easiest in this case, is to not rely on the default UI, and instead move all your Files to a conventional Array, that you will be able to edit as you wish.
Here is one messy way, but it may give you a good starting point :
It does add an Array to your input that will keep in memory the files added.
// The multiUp function will be called each time our hidden input is changed
document.querySelector('input').addEventListener('change', multiUp, false);
function multiUp(e){
// if it's the first time we call it
if(!this.multiFiles){
// create the array that will keep our files in memory
this.multiFiles = [];
// add a pointer to the span where we'll display the file names
this.__fileHolder = document.querySelector('#fileHolder');
}
// each time : empty the fileHolder span
this.__fileHolder.innerHTML = '';
var i;
// add the new files to our array
for(i = 0; i<this.files.length; i++){
this.multiFiles.push(this.files[i]);
}
for(i = 0; i<this.multiFiles.length; i++){
// display every file name to our fileHolder
this.__fileHolder.appendChild(document.createTextNode(this.multiFiles[i].name) );
// add a button to remove the file from the list
addDeleteBtn(i, this);
}
}
// Tell the add span to act as a trigger to our input
document.querySelector('#add').addEventListener('click', function(){ document.querySelector('input').click()}, false);
function addDeleteBtn(f, input){
// create the element
var del= document.createElement('span');
del.innerHTML = ' (x) ';
del.className = 'deleteBtn';
del.title = 'remove this file';
// add an onclick event
del.addEventListener('click', function(){
// update the array
input.multiFiles.splice(f, 1);
// update the fileHodler
input.__fileHolder.innerHTML = '';
var fileLength = input.multiFiles.length;
if(fileLength>0){
for(var i = 0; i<fileLength; i++){
input.__fileHolder.appendChild(document.createTextNode(input.multiFiles[i].name) );
addDeleteBtn(i, input);
}
}
else input.__fileHolder.innerHTML = 'No files selected.';
}, false);
input.__fileHolder.appendChild(del);
}
#add{ font-size: 2em; cursor: pointer;}
#fileHolder{ color: rgba(0,0,0,.7); max-width: 80%; font-size: 70%; overflow-x: auto; white-space: nowrap; display: inline-block;}
.deleteBtn{cursor: pointer; color: #000;}
<div class="multiUp">
<span id="add">+</span>
<span id="fileHolder">No files selected.</span>
<input multiple type="file" style="display: none"/>
</div>
You may now access those files by iterating through document.querySelector('.multiUp>input').multiFiles.
I have an website which uses an input type=file to upload some files.
After uploading I don't want to display the file selection, so I am going to delete it by calling event.target.value = null.
function viewModel() {
var self = this;
self.addAudioFile = function(data, event) {
event.preventDefault();
var context = ko.contextFor(event.target);
var selectedFile = event.target.files[0];
var targetPrompt = data.prompt;
console.log("aa");
event.target.value = null;
}
}
$(document).ready(function() {
var newModel = new viewModel();
ko.applyBindings(newModel);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="btn-link prompt-audio-control">
<div class="btn-file">
<input type="file" data-bind="event: {change: $root.addAudioFile}" accept="audio/wav|audio/pcm|audio|vox">
<span> Upload </span>
</div>
</div>
What confuses me here is when I used Chrome to test, the function addAudioFile is executed only one time, but when I used IE, it executed this function 2 times.
I used the console.log("aa") in oder to test it. Can anyone explain me why this happens?
It was triggered twice due to this call.
event.target.value = null;
After that line, the change event is fired again.
Try putting another console.log after that line so you can see the effect.
Something like:
event.target.value = null;
console.log("bb");
So in order to stop the function from continuing its second call you can trap the value of event.target.value.
self.addAudioFile = function(data, event) {
if(event.target.value === ""){
return;
}
... rest of code here ...
}
Why is it checked as a string and not a null? That part i'm not sure but the browsers engine are converting it into a blank string hence the condition.
And on the part where chrome doesn't fire twice, i'll just leave this to browser experts, but my guess is chrome engine is set to not change the file if a null value is given (just my wild guess though).
==================
And lastly, you can use knockout this way but you "should" not do it that way :D
See Tomalak's answer on how to separate knockout's DOM concerns using custom bindings.
As a matter of principle, whenever you find that you access DOM elements, events or even the binding context in a knockout viewmodel then you are doing something wrong.
The only place in a knockout application where you interact with the DOM is the binding handler. If there is no binding handler that does what you need, write one.
// exemplary binding handler ---------------------------------------------------------
ko.bindingHandlers.file = {
init: function (element, valueAccessor) {
if ( !ko.isWriteableObservable(valueAccessor()) ) return;
// alternatively $(element).on("change", ...
ko.utils.registerEventHandler(element, "change", function (e) {
var observable = valueAccessor();
observable(e.target.files[0]);
element.value = null;
e.preventDefault();
});
}
};
// usage -----------------------------------------------------------------------------
function FileUploader() {
var self = this;
self.audioFile = ko.observable();
}
$(function() {
var viewModel = new FileUploader();
ko.applyBindings(viewModel);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="btn-link prompt-audio-control">
<div class="btn-file">
<input type="file" data-bind="file: audioFile" accept="audio/wav|audio/pcm|audio|vox">
<span> Upload </span>
</div>
</div>
<hr>
View Model:
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
If setting the <input type="file">'s value to null is a good idea is another matter, since the event's files collection contains information about the files the user selected, it does not contain the files themselves. Setting the form control to null effectively destroys the user selection. If you want to actually upload the file at some point, more plumbing will be necessary.
What i undestand:
You want to "hide" the text about "what file you upload" after upload the file... (what redundant phrase lol), maybe this could help you instead "erasing" the selection.
Creating a HTML Button:
.fileUpload {
position: relative;
overflow: hidden;
margin: 10px;
}
.fileUpload input.upload {
position: absolute;
top: 0;
left: 0;
margin: 0;
padding: 0;
font-size: 20px;
cursor: pointer;
opacity: 0;
filter: alpha(opacity=0);
}
<div class="fileUpload btn btn-primary">
<span>Upload</span>
<input type="file" class="upload" />
</div>
I am building a BackboneJS/Marionette App and currently trying to allow users to upload multiple files.
This works when they select multiple files at the same time but I would like to have the functionality to allow them to select 1 file and then click the input again and add to the original FileList Object if possible.
Or I would like to find a way to allow my save function to grab the files from multiple inputs if need be.
I am open to any and all suggestions
This is the HTML5 File API code I am using and I am using the jquery/js offered by the MDN HTML5 File API guide
<input id="uploads" name="uploads" type="file" class="file uploads file1" multiple style="display: none"/>
Select File 1<br>
A FileList object from an HTMLInputElement (which is the underlying object that will hold the Files in your input.files) can only be modified in order to clear it completely (with input.value = null).
There are now ways to actually circumvent this, introduced by the DataTransfer constructor, but as of today, only Chrome and latest Firefox do support this constructor.
So the easiest in this case, is to not rely on the default UI, and instead move all your Files to a conventional Array, that you will be able to edit as you wish.
Here is one messy way, but it may give you a good starting point :
It does add an Array to your input that will keep in memory the files added.
// The multiUp function will be called each time our hidden input is changed
document.querySelector('input').addEventListener('change', multiUp, false);
function multiUp(e){
// if it's the first time we call it
if(!this.multiFiles){
// create the array that will keep our files in memory
this.multiFiles = [];
// add a pointer to the span where we'll display the file names
this.__fileHolder = document.querySelector('#fileHolder');
}
// each time : empty the fileHolder span
this.__fileHolder.innerHTML = '';
var i;
// add the new files to our array
for(i = 0; i<this.files.length; i++){
this.multiFiles.push(this.files[i]);
}
for(i = 0; i<this.multiFiles.length; i++){
// display every file name to our fileHolder
this.__fileHolder.appendChild(document.createTextNode(this.multiFiles[i].name) );
// add a button to remove the file from the list
addDeleteBtn(i, this);
}
}
// Tell the add span to act as a trigger to our input
document.querySelector('#add').addEventListener('click', function(){ document.querySelector('input').click()}, false);
function addDeleteBtn(f, input){
// create the element
var del= document.createElement('span');
del.innerHTML = ' (x) ';
del.className = 'deleteBtn';
del.title = 'remove this file';
// add an onclick event
del.addEventListener('click', function(){
// update the array
input.multiFiles.splice(f, 1);
// update the fileHodler
input.__fileHolder.innerHTML = '';
var fileLength = input.multiFiles.length;
if(fileLength>0){
for(var i = 0; i<fileLength; i++){
input.__fileHolder.appendChild(document.createTextNode(input.multiFiles[i].name) );
addDeleteBtn(i, input);
}
}
else input.__fileHolder.innerHTML = 'No files selected.';
}, false);
input.__fileHolder.appendChild(del);
}
#add{ font-size: 2em; cursor: pointer;}
#fileHolder{ color: rgba(0,0,0,.7); max-width: 80%; font-size: 70%; overflow-x: auto; white-space: nowrap; display: inline-block;}
.deleteBtn{cursor: pointer; color: #000;}
<div class="multiUp">
<span id="add">+</span>
<span id="fileHolder">No files selected.</span>
<input multiple type="file" style="display: none"/>
</div>
You may now access those files by iterating through document.querySelector('.multiUp>input').multiFiles.