(first off, I'm very new to JavaScript - HTML/PHP I know a little better)
I would like to send the table data (which is dynamically created by javascript - depending on how many students there are in the database) to a PHP file to process. The table is created and added to HTML-id "create_groups" (which is included in a HTML Form) after the button "Create Groups" has been pressed.
I've tried giving each table row it's own name (tr.setAttribute('name', students[i][col[0]]);) which works (at least the browser shows it in the "console" window) but when I press "submit" the Javascript table data isn't transmitted to the create_groups.php. At least I can't seem to get hold of it.
teacher.php
function createTableFromJSON() {
hideAll();
document.getElementById('create_groups').style.display = "block";
let con = new XMLHttpRequest(); //Create Object
console.log("1");
con.open("GET", "teacher_check.php", true); //open the connection
con.onreadystatechange = function() { //define Callback function
if (con.readyState == 4 && con.status == 200) {
console.log("2");
console.log(this.responseText);
let response = this.responseText;
let students = JSON.parse(this.responseText);
//Convert String back into JSON object
console.log(students);
let col = [];
for (let key in students[0]) {
col.push(key);
}
// CREATE DYNAMIC TABLE.
let table = document.createElement("table");
// CREATE HTML TABLE HEADER ROW USING THE EXTRACTED HEADERS ABOVE.
let tr = table.insertRow(-1); // TABLE ROW AT THE END
let th = document.createElement("th");
th.innerHTML = "SELECT";
tr.appendChild(th);
for (let i = 0; i < col.length; i++) {
let th = document.createElement("th"); // TABLE HEADER.
th.innerHTML = col[i];
tr.appendChild(th);
}
// ADD JSON DATA TO THE TABLE AS ROWS.
for (let i = 0; i < students.length; i++) {
tr = table.insertRow(-1);
var checkbox = document.createElement('input');
checkbox.type = "checkbox";
console.log(students[i][col[0]]);
//this shows the right names
tr.appendChild(checkbox);
tr.setAttribute('name', students[i][col[0]]);
for (let j = 0; j < col.length; j++) {
let tabCell = tr.insertCell(-1);
tabCell.innerHTML = students[i][col[j]];
}
}
// FINALLY ADD THE NEWLY CREATED TABLE WITH JSON DATA TO A CONTAINER.
let divContainer = document.getElementById("create_groups");
//divContainer.innerHTML = "";
divContainer.appendChild(table);
document.getElementById("create_groups").submit();
}
}
con.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); //Set appropriate request Header
con.send(); //Request File Content
}
function hideAll() {
document.getElementById('create_groups').style.display = "none";
}
<input type="button" name="create_groups" value="Create Groups" onclick="createTableFromJSON()">
<form action="create_groups.php" method ="post">
<div class="container white darken-4" id="create_groups" style="display:none;">
<p>
<label>
<span>Groupname:</span>
<input type="text" name="groupname">
</label>
</p>
<p><button type="submit" name ="submit">Submit</button></p>
</div>
create_groups.php
if (isset($_POST['submit'])) {
$student = $_POST['stud'];
$groupname = $_POST['groupname'];
echo $groupname;
echo $student;
}
I expect to be able to access all the names of the students in the create_groups.php file, which are checked off in the other teacher.php file in the table. Obviously the "isChecked" part isn't implemented yet, because the other part doesn't work yet.
So the creating of the table with the correct data works, just not the transmitting to the PHP file.
There is few issues in html syntax and javascript as well please try correcting them.
hideAll() function definition is missing in you script first line.
Second in html before tag there is unnecessary ">" character.
I'm working to a homework and I wrote the frontend in html, css, javascript. Till now when I press a button I get some data from backend and in javascript a parse the response. The response is an array of items. An item is a structure. What I want it's to build dynamically a list of those items. I didn't find on google a way of doing that with javascript. Some hints/help?
What I tried, you can see below, is to append some HTML to an HTML element - it didn't work.
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
source.value = "";
destination.value = "";
var array_rides = JSON.parse(this.responseText).results;
var rides_length = array_rides.length;
for (var i = 0;i < rides_length;i++) {
console.log(array_rides[i][DRIVER]);
console.log(array_rides[i][RIDERS]);
console.log(array_rides[i][SOURCE]);
console.log(array_rides[i][DESTINATION]);
console.log(array_rides[i][START]);
console.log(array_rides[i][SEATS_AVAILABLE]);
console.log(array_rides[i][SEATS_TOTAL]);
my_list.append('<span class="name">{0}</span>'.format(array_rides[i][DRIVER]));
}
}
};
So, I want a list which is dynamically populated.
Something like (table ish):
array_rides[0][DRIVER], array_rides[0][RIDERS], ...
array_rides[1][DRIVER], array_rides[1][RIDERS], ...
...
array_rides[n][DRIVER], array_rides[n][RIDERS], ...
which, of cours, to inherit some css.
I assume a list in the document, like a product table or something.
The easiest way to do this is by just looping through your list and inserting it into a table or something. An example could be this:
function somefunc() {
var table = document.getElementById('my_table');
var array_rides = ['cell1', 'cell2', 'cell3', 'cell4'];
var string;
string = "<table>";
for (var i = 0;i < array_rides.length;i++) {
//add table row
string += "<tr>";
//add all items in tabel cells
//you just have to replace the array_rides[i] with array_rides[i].DRIVER, array_rides[i].RIDERS... and so on
string += "<td>"+array_rides[i]+"</td>";
string += "<td>"+array_rides[i]+"</td>";
string += "<td>"+array_rides[i]+"</td>";
string += "<td>"+array_rides[i]+"</td>";
string += "<td>"+array_rides[i]+"</td>";
string += "<td>"+array_rides[i]+"</td>";
//close table row
string += "</tr>";
}
string += "</table>";
table.innerHTML = string;
}
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
th {
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #dddddd;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Testpage</title>
</head>
<body onload="somefunc();">
<div id="my_table"></div>
</body>
</html>
basically what this does is take all the data from the array and append them to a table.
You could add some CSS too to make it look nicer
I have this problem here
The problem has been solved, but my question is how can I get the second value from that, or the third one. The sheet will have many tables and at some point I will need a total for each table. Also, is there any solution to automatically find the the array number which contain date row for each table (instead defining this manually). Hope my explanation make sense.
Thank you!
Kind regards,
L.E. Test file
If I understood your question correctly, instead of breaking the loop when a match to "Total" is found do whatever is needed to be done within the loop like so...
var today = toDateFormat(new Date());
var todaysColumn =
values[5].map(toDateFormat).map(Number).indexOf(+today);
var emailDate = Utilities.formatDate(new Date(today),"GMT+1",
"dd/MM/yyyy");
for (var i=0; i<values.length; i++){
if (values[i][0]=='Total'){
nr = i;
Logger.log(nr);
var output = values[nr][todaysColumn];
// Do something with the output here I"m assuming you email it
}
}
The loop will keep going and find every "Total" and do the same thing. This answer assumes that the "Totals" are in the same column. You can get fancier with this if you only want certain tables to send and not others, but this should get you started.
I didn't quite understand the second part of your question...
"Also, is there any solution to automatically find the the array
number which contain date row for each table (instead defining this
manually). Hope my explanation make sense."
I'm guessing you want all the rows that contain "Total" in the specific column. You could instantiate a variable as an empty array like so, var totals = [];. Then instead of sending the email or whatever in the first loop you would push the row values to the array like so, totals.push(nr+1) . //adding 1 gives you the actual row number (rows count from 1 but arrays count from 0). You could then simply loop through the totals array and do whatever you wanted to do. Alternatively you could create an array of all the values instead of row numbers like totals.push(values[nr][todaysColumn]) and loop through that array. Lots of ways to solve this problem!
Ok based on our conversation below I've edited the "test" sheet and updated the code. Below are my edits
All edits have been made in your test sheet and verified working in Logger. Let me know if you have any questions.
Spreadsheet:
Added "Validation" Tab
Edited "Table" tab so the row with "Email Address" in Column A lines up with the desired lookup values (dates or categories)...this was only for the first two tables as all the others already had this criteria.
Code:
Create table/category selector...
In the editor go to File >> New >> HTMLfile
Name the file "inputHTML"
Copy and paste the following code into that file
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<form class="notice_form" autocomplete="off" onsubmit="formSubmit(this)" target="hidden_iframe">
<select id="tables" onchange="hideunhideCatagory(this.value)" required></select>
<p></p>
<select id="categories" style="display:none"></select>
<hr/>
<button class="submit" type="submit">Get Total</button>
</form>
<script>
window.addEventListener('load', function() {
console.log('Page is loaded');
});
</script>
<script
src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript">
// The code in this function runs when the page is loaded.
$(function() {
var tableRunner = google.script.run.withSuccessHandler(buildTableList);
var catagoryRunner = google.script.run.withSuccessHandler(buildCatagoryList);
tableRunner.getTables();
catagoryRunner.getCategories();
});
function buildTableList(tables) {
var list = $('#tables');
list.empty();
list.append('<option></option>');
for (var i = 0; i < tables.length; i++) {
if(tables[i]==''){break;}
list.append('<option>' + tables[i] + '</option>');
}
}
function buildCatagoryList(categories) {
var list = $('#categories');
list.empty();
list.append('<option></option>');
for (var i = 0; i < categories.length; i++) {
if(categories[i]==''){break;}
list.append('<option>' + categories[i] + '</option>');
}
}
function hideunhideCatagory(tableValue){
var catElem = document.getElementById("categories");
if(tableValue == "Total Calls By Date" || tableValue == "Total Appointments by Date"){
catElem.style.display = "none"
document.required = false;
}else{
catElem.style.display = "block"
document.required = true;
}
}
function formSubmit(argTheFormElement) {
var table = $("select[id=tables]").val(),
catagory = $("select[id=categories]").val();
console.log(table)
google.script.run
.withSuccessHandler(google.script.host.close)
.getTotal(table,catagory);
}
</script>
</body>
<div id="hiframe" style="display:block; visibility:hidden; float:right">
<iframe name="hidden_iframe" height="0px" width="0px" ></iframe>
</div>
</html>
Edits to Code.gs file
Replace code in Code.gs with this...
//This is a simple trigger that creates the menu item in your sheet
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Run Scripts Manually')
.addItem('Get Total','fncOpenMyDialog')
.addToUi();
}
//This function launches the dialog and is launched by the menu item
function fncOpenMyDialog() {
//Open a dialog
var htmlDlg = HtmlService.createHtmlOutputFromFile('inputHTML')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setWidth(200)
.setHeight(150);
SpreadsheetApp.getUi()
.showModalDialog(htmlDlg, 'Select table to get total for');
};
//main function called by clicking "Get Total" on the dialogue...variables are passed to this function from the formSubmit in the inputHTML javascript
function getTotal(table,catagory) {
function toDateFormat(date) {
try {return date.setHours(0,0,0,0);}
catch(e) {return;}
}
//get all values
var values = SpreadsheetApp
.openById("10pB0jDPG8HYolECQ3eg1lrOFjXQ6JRFwQ-llvdE2yuM")
.getSheetByName("Tables")
.getDataRange()
.getValues();
//declare/instantiate your variables
var tableHeaderRow, totalRow, tableFound = false;
//begin loop through column A in Tables Sheet
for (var i = 0; i<values.length; i++){
//test to see if values have already been found if so break the loop
if(tableFound == true){break;}
//check to see if value matches selected table
if (values[i][0]==table){
//start another loop immediately after the match row
for(var x=i+1; x<values.length; x++){
if(values[x][0] == "Email Address"){ //This header needs to consistantly denote the row that contains the headers
tableHeaderRow = x;
tableFound = true;
}else if(values[x][0] == "Total"){
totalRow = x;
break;
}
}
}
}
Logger.log("Header Row = "+tableHeaderRow)
Logger.log("Total Row = "+ totalRow)
var today = toDateFormat(new Date())
var columnToTotal;
if(catagory==''){
columnToTotal = values[tableHeaderRow].map(toDateFormat).map(Number).indexOf(+today);
}else{
columnToTotal = values[tableHeaderRow].indexOf(catagory);
}
var output = values[totalRow][columnToTotal];
Logger.log(output);
var emailDate = Utilities.formatDate(new Date(today),"GMT+1", "dd/MM/yyyy");
//here is where you would put your code to do something with the output
}
/** The functions below are used by the form to populate the selects **/
function getTables(){
var cFile = SpreadsheetApp.getActive();
var cSheet = cFile.getSheetByName('Validation');
var cSheetHeader = cSheet.getRange(1,1,cSheet.getLastRow(),cSheet.getLastColumn()).getValues().shift();
var tabelCol = (cSheetHeader.indexOf("Tables")+1);
var tables = cSheet.getRange(2,tabelCol,cSheet.getLastRow(),1).getValues();
return tables.filter(function (elem){
return elem != "";
});
}
function getCatagories(){
var cFile = SpreadsheetApp.getActive();
var cSheet = cFile.getSheetByName('Validation');
var cSheetHeader = cSheet.getRange(1,1,cSheet.getLastRow(),cSheet.getLastColumn()).getValues().shift();
var catagoriesCol = (cSheetHeader.indexOf("Catagory")+1);
var catagories = cSheet.getRange(2,catagoriesCol,cSheet.getLastRow(),1).getValues();
return catagories.filter(function (elem){
return elem != "";
});
}
Im starting to do some small functions and tweaks on websites with javascript, but whats really bothers me is that I dont know how to run the javascript again after a function has run?
For instance if I call a function onclick which adds a user to an array that is shown in my website, the new user wont be displayed until the page is refreshed?
How do I work around this?
EXAMPLE:
if (!localStorage.myStorage) {
// CREATE LOCALSTORAGE
}else{
myArray = JSON.parse(localStorage.myStorage);
for (var i = 0; i < myArray.length; i++) {
if(myArray[i].id === 1){
$(".firstIdContainer").append("<p>" + myArray[i].userName + "</p>");
}
if(aUserLogin[i].id === 2) {
$(".secondIdContainer").append("<p>" + myArray[i].userName + "</p>");
}
}
}
$(document).on("click", ".btnRegisterUser", function() {
// ADD NEW USER TO LOCALSTORAGE
}
How do i make sure my new user i register will be shown immediately through my for loop displaying users.
Like:
if(!localStorage.myStorage){
// CREATE LOCALSTORAGE
}
function doIt(){
var myArray = JSON.parse(localStorage.myStorage);
for(var i in myArray){
var apd = '<p>' + myArray[i].userName + '</p>';
if(myArray[i].id === 1){
$(".firstIdContainer").append(apd);
}
else if(aUserLogin[i].id === 2) {
$(".secondIdContainer").append(apd);
}
}
}
}
doIt();
$('.btnRegisterUser').click(doIt);
Try creating a contentUpdate function that resets whatever is getting displayed and creates it again based on new variables (this would go at the bottom of a function to add the user, for example). The reason that variable changes aren't reflected in the DOM is that the DOM has no abstraction for how it was made; it's output, and it won't change itself based on what its input has done after it was put in.
If you just want to insert a new row into a table you don't need to refresh the page.
jsfiddle
html:
<table id="usertable">
<tr><td>user 1</td></tr>
</table>
<input id="newuser"></input>
<input id="adduser" type="submit"></input>
js:
var button = document.getElementById('adduser');
button.onclick = function(event) {
var user = document.getElementById('newuser').value
//add the user to your array here
//add a table row
var table = document.getElementById('usertable');
var row = table.insertRow(0);
var cell1 = row.insertCell(0);
cell1.innerHTML = user;
event.preventDefault();
}
I'm building a drag-and-drop-to-upload web application using HTML5, and I'm dropping the files onto a div and of course fetching the dataTransfer object, which gives me the FileList.
Now I want to remove some of the files, but I don't know how, or if it's even possible.
Preferably I'd like to just delete them from the FileList; I've got no use for them. But if that's not possible, should I instead write in checks in code that interacts with the FileList? That seems cumbersome.
If you want to delete only several of the selected files: you can't. The File API Working Draft you linked to contains a note:
The HTMLInputElement interface
[HTML5] has a readonly FileList
attribute, […]
[emphasis mine]
Reading a bit of the HTML 5 Working Draft, I came across the Common input element APIs. It appears you can delete the entire file list by setting the value property of the input object to an empty string, like:
document.getElementById('multifile').value = "";
BTW, the article Using files from web applications might also be of interest.
Since JavaScript FileList is readonly and cannot be manipulated directly,
BEST METHOD
You will have to loop through the input.files while comparing it with the index of the file you want to remove. At the same time, you will use new DataTransfer() to set a new list of files excluding the file you want to remove from the file list.
With this approach, the value of the input.files itself is changed.
removeFileFromFileList(index) {
const dt = new DataTransfer()
const input = document.getElementById('files')
const { files } = input
for (let i = 0; i < files.length; i++) {
const file = files[i]
if (index !== i)
dt.items.add(file) // here you exclude the file. thus removing it.
}
input.files = dt.files // Assign the updates list
}
ALTERNATIVE METHOD
Another simple method is to convert the FileList into an array and then splice it.
But this approach will not change the input.files
const input = document.getElementById('files')
// as an array, u have more freedom to transform the file list using array functions.
const fileListArr = Array.from(input.files)
fileListArr.splice(index, 1) // here u remove the file
console.log(fileListArr)
This question has already been marked answered, but I'd like to share some information that might help others with using FileList.
It would be convenient to treat a FileList as an array, but methods like sort, shift, pop, and slice don't work. As others have suggested, you can copy the FileList to an array. However, rather than using a loop, there's a simple one line solution to handle this conversion.
// fileDialog.files is a FileList
var fileBuffer=[];
// append the file list to an array
Array.prototype.push.apply( fileBuffer, fileDialog.files ); // <-- here
// And now you may manipulated the result as required
// shift an item off the array
var file = fileBuffer.shift(0,1); // <-- works as expected
console.info( file.name + ", " + file.size + ", " + file.type );
// sort files by size
fileBuffer.sort(function(a,b) {
return a.size > b.size ? 1 : a.size < b.size ? -1 : 0;
});
Tested OK in FF, Chrome, and IE10+
If you are targeting evergreen browsers (Chrome, Firefox, Edge, but also works in Safari 9+) or you can afford a polyfill, you can turn the FileList into an array by using Array.from() like this:
let fileArray = Array.from(fileList);
Then it's easy to handle the array of Files like any other array.
Since we are in the HTML5 realm, this is my solution. The gist is that you push the files to an Array instead of leaving them in a FileList, then using XHR2, you push the files to a FormData object. Example below.
Node.prototype.replaceWith = function(node)
{
this.parentNode.replaceChild(node, this);
};
if(window.File && window.FileList)
{
var topicForm = document.getElementById("yourForm");
topicForm.fileZone = document.getElementById("fileDropZoneElement");
topicForm.fileZone.files = new Array();
topicForm.fileZone.inputWindow = document.createElement("input");
topicForm.fileZone.inputWindow.setAttribute("type", "file");
topicForm.fileZone.inputWindow.setAttribute("multiple", "multiple");
topicForm.onsubmit = function(event)
{
var request = new XMLHttpRequest();
if(request.upload)
{
event.preventDefault();
topicForm.ajax.value = "true";
request.upload.onprogress = function(event)
{
var progress = event.loaded.toString() + " bytes transfered.";
if(event.lengthComputable)
progress = Math.round(event.loaded / event.total * 100).toString() + "%";
topicForm.fileZone.innerHTML = progress.toString();
};
request.onload = function(event)
{
response = JSON.parse(request.responseText);
// Handle the response here.
};
request.open(topicForm.method, topicForm.getAttribute("action"), true);
var data = new FormData(topicForm);
for(var i = 0, file; file = topicForm.fileZone.files[i]; i++)
data.append("file" + i.toString(), file);
request.send(data);
}
};
topicForm.fileZone.firstChild.replaceWith(document.createTextNode("Drop files or click here."));
var handleFiles = function(files)
{
for(var i = 0, file; file = files[i]; i++)
topicForm.fileZone.files.push(file);
};
topicForm.fileZone.ondrop = function(event)
{
event.stopPropagation();
event.preventDefault();
handleFiles(event.dataTransfer.files);
};
topicForm.fileZone.inputWindow.onchange = function(event)
{
handleFiles(topicForm.fileZone.inputWindow.files);
};
topicForm.fileZone.ondragover = function(event)
{
event.stopPropagation();
event.preventDefault();
};
topicForm.fileZone.onclick = function()
{
topicForm.fileZone.inputWindow.focus();
topicForm.fileZone.inputWindow.click();
};
}
else
topicForm.fileZone.firstChild.replaceWith(document.createTextNode("It's time to update your browser."));
I have found very quick & short workaround for this. Tested in many popular browsers (Chrome, Firefox, Safari);
First, you have to convert FileList to an Array
var newFileList = Array.from(event.target.files);
to delete the particular element use this
newFileList.splice(index,1);
I know this is an old question but it's ranking high on search engines in regards to this issue.
properties in the FileList object cannot be deleted but at least on Firefox they can be changed. My workaround this issue was to add a property IsValid=true to those files that passed check and IsValid=false to those that didn't.
then I just loop through the list to make sure that only the properties with IsValid=true are added to FormData.
This is extemporary, but I had the same problem which I solved this way. In my case I was uploading the files via XMLHttp request, so I was able to post the FileList cloned data through formdata appending. Functionality is that you can drag and drop or select multiple files as many times as you want (selecting files again won't reset the cloned FileList), remove any file you want from the (cloned) file list, and submit via xmlhttprequest whatever was left there. This is what I did. It is my first post here so code is a little messy. Sorry. Ah, and I had to use jQuery instead of $ as it was in Joomla script.
// some global variables
var clon = {}; // will be my FileList clone
var removedkeys = 0; // removed keys counter for later processing the request
var NextId = 0; // counter to add entries to the clone and not replace existing ones
jQuery(document).ready(function(){
jQuery("#form input").change(function () {
// making the clone
var curFiles = this.files;
// temporary object clone before copying info to the clone
var temparr = jQuery.extend(true, {}, curFiles);
// delete unnecessary FileList keys that were cloned
delete temparr["length"];
delete temparr["item"];
if (Object.keys(clon).length === 0){
jQuery.extend(true, clon, temparr);
}else{
var keysArr = Object.keys(clon);
NextId = Math.max.apply(null, keysArr)+1; // FileList keys are numbers
if (NextId < curFiles.length){ // a bug I found and had to solve for not replacing my temparr keys...
NextId = curFiles.length;
}
for (var key in temparr) { // I have to rename new entries for not overwriting existing keys in clon
if (temparr.hasOwnProperty(key)) {
temparr[NextId] = temparr[key];
delete temparr[key];
// meter aca los cambios de id en los html tags con el nuevo NextId
NextId++;
}
}
jQuery.extend(true, clon, temparr); // copy new entries to clon
}
// modifying the html file list display
if (NextId === 0){
jQuery("#filelist").html("");
for(var i=0; i<curFiles.length; i++) {
var f = curFiles[i];
jQuery("#filelist").append("<p id=\"file"+i+"\" style=\'margin-bottom: 3px!important;\'>" + f.name + "<a style=\"float:right;cursor:pointer;\" onclick=\"BorrarFile("+i+")\">x</a></p>"); // the function BorrarFile will handle file deletion from the clone by file id
}
}else{
for(var i=0; i<curFiles.length; i++) {
var f = curFiles[i];
jQuery("#filelist").append("<p id=\"file"+(i+NextId-curFiles.length)+"\" style=\'margin-bottom: 3px!important;\'>" + f.name + "<a style=\"float:right;cursor:pointer;\" onclick=\"BorrarFile("+(i+NextId-curFiles.length)+")\">x</a></p>"); // yeap, i+NextId-curFiles.length actually gets it right
}
}
// update the total files count wherever you want
jQuery("#form p").text(Object.keys(clon).length + " file(s) selected");
});
});
function BorrarFile(id){ // handling file deletion from clone
jQuery("#file"+id).remove(); // remove the html filelist element
delete clon[id]; // delete the entry
removedkeys++; // add to removed keys counter
if (Object.keys(clon).length === 0){
jQuery("#form p").text(Object.keys(clon).length + " file(s) selected");
jQuery("#fileToUpload").val(""); // I had to reset the form file input for my form check function before submission. Else it would send even though my clone was empty
}else{
jQuery("#form p").text(Object.keys(clon).length + " file(s) selected");
}
}
// now my form check function
function check(){
if( document.getElementById("fileToUpload").files.length == 0 ){
alert("No file selected");
return false;
}else{
var _validFileExtensions = [".pdf", ".PDF"]; // I wanted pdf files
// retrieve input files
var arrInputs = clon;
// validating files
for (var i = 0; i < Object.keys(arrInputs).length+removedkeys; i++) {
if (typeof arrInputs[i]!="undefined"){
var oInput = arrInputs[i];
if (oInput.type == "application/pdf") {
var sFileName = oInput.name;
if (sFileName.length > 0) {
var blnValid = false;
for (var j = 0; j < _validFileExtensions.length; j++) {
var sCurExtension = _validFileExtensions[j];
if (sFileName.substr(sFileName.length - sCurExtension.length, sCurExtension.length).toLowerCase() == sCurExtension.toLowerCase()) {
blnValid = true;
break;
}
}
if (!blnValid) {
alert("Sorry, " + sFileName + " is invalid, allowed extensions are: " + _validFileExtensions.join(", "));
return false;
}
}
}else{
alert("Sorry, " + arrInputs[0].name + " is invalid, allowed extensions are: " + _validFileExtensions.join(" or "));
return false;
}
}
}
// proceed with the data appending and submission
// here some hidden input values i had previously set. Now retrieving them for submission. My form wasn't actually even a form...
var fecha = jQuery("#fecha").val();
var vendor = jQuery("#vendor").val();
var sku = jQuery("#sku").val();
// create the formdata object
var formData = new FormData();
formData.append("fecha", fecha);
formData.append("vendor", encodeURI(vendor));
formData.append("sku", sku);
// now appending the clone file data (finally!)
var fila = clon; // i just did this because I had already written the following using the "fila" object, so I copy my clone again
// the interesting part. As entries in my clone object aren't consecutive numbers I cannot iterate normally, so I came up with the following idea
for (i = 0; i < Object.keys(fila).length+removedkeys; i++) {
if(typeof fila[i]!="undefined"){
formData.append("fileToUpload[]", fila[i]); // VERY IMPORTANT the formdata key for the files HAS to be an array. It will be later retrieved as $_FILES['fileToUpload']['temp_name'][i]
}
}
jQuery("#submitbtn").fadeOut("slow"); // remove the upload btn so it can't be used again
jQuery("#drag").html(""); // clearing the output message element
// start the request
var xhttp = new XMLHttpRequest();
xhttp.addEventListener("progress", function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
}, false);
if ( xhttp.upload ) {
xhttp.upload.onprogress = function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
var percent = done / total;
jQuery("#drag").html(Math.round(percent * 100) + "%");
};
}
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var respuesta = this.responseText;
jQuery("#drag").html(respuesta);
}
};
xhttp.open("POST", "your_upload_handler.php", true);
xhttp.send(formData);
return true;
}
};
Now the html and styles for this. I'm quite a newbie but all this actually worked for me and took me a while to figure it out.
<div id="form" class="formpos">
<!-- Select the pdf to upload:-->
<input type="file" name="fileToUpload[]" id="fileToUpload" accept="application/pdf" multiple>
<div><p id="drag">Drop your files here or click to select them</p>
</div>
<button id="submitbtn" onclick="return check()" >Upload</button>
// these inputs are passed with different names on the formdata. Be aware of that
// I was echoing this, so that's why I use the single quote for php variables
<input type="hidden" id="fecha" name="fecha_copy" value="'.$fecha.'" />
<input type="hidden" id="vendor" name="vendorname" value="'.$vendor.'" />
<input type="hidden" id="sku" name="sku" value="'.$sku.'"" />
</div>
<h1 style="width: 500px!important;margin:20px auto 0px!important;font-size:24px!important;">File list:</h1>
<div id="filelist" style="width: 500px!important;margin:10px auto 0px!important;">Nothing selected yet</div>
The styles for that. I had to mark some of them !important to override Joomla behavior.
.formpos{
width: 500px;
height: 200px;
border: 4px dashed #999;
margin: 30px auto 100px;
}
.formpos p{
text-align: center!important;
padding: 80px 30px 0px;
color: #000;
}
.formpos div{
width: 100%!important;
height: 100%!important;
text-align: center!important;
margin-bottom: 30px!important;
}
.formpos input{
position: absolute!important;
margin: 0!important;
padding: 0!important;
width: 500px!important;
height: 200px!important;
outline: none!important;
opacity: 0!important;
}
.formpos button{
margin: 0;
color: #fff;
background: #16a085;
border: none;
width: 508px;
height: 35px;
margin-left: -4px;
border-radius: 4px;
transition: all .2s ease;
outline: none;
}
.formpos button:hover{
background: #149174;
color: #0C5645;
}
.formpos button:active{
border:0;
}
I hope this helps.
Thanks #Nicholas Anderson simple and straight , here is your code applied and working at my code using jquery.
HTML .
<input class="rangelog btn border-aero" id="file_fr" name="file_fr[]" multiple type="file" placeholder="{$labels_helpfiles_placeholder_file}">
<span style="cursor: pointer; cursor: hand;" onclick="cleanInputs($('#file_fr'))"><i class="fa fa-trash"></i> Empty chosen files</span>
JS CODE
function cleanInputs(fileEle){
$(fileEle).val("");
var parEle = $(fileEle).parent();
var newEle = $(fileEle).clone()
$(fileEle).remove();
$(parEle).prepend(newEle);
}
There might be a more elegant way to do this but here is my solution. With Jquery
fileEle.value = "";
var parEle = $(fileEle).parent();
var newEle = $(fileEle).clone()
$(fileEle).remove();
parEle.append(newEle);
Basically you cleat the value of the input. Clone it and put the clone in place of the old one.
If you have the luck to be sending a post request to the database with the files and you have the files you want to send in your DOM
you can simply check if the file in the file list is present in your DOM, and of course if it's not you just don't send that element to de DB.
I realize this is a pretty old question, however I am using an html multiple file selection upload to queue any number of files which can be selectively removed in a custom UI before submitting.
Save files in a variable like this:
let uploadedFiles = [];
//inside DOM file select "onChange" event
let selected = e.target.files[0] ? e.target.files : [];
uploadedFiles = [...uploadedFiles , ...selected ];
createElements();
Create UI with "remove a file":
function createElements(){
uploadedFiles.forEach((f,i) => {
//remove DOM elements and re-create them here
/* //you can show an image like this:
* let reader = new FileReader();
* reader.onload = function (e) {
* let url = e.target.result;
* // create <img src=url />
* };
* reader.readAsDataURL(f);
*/
element.addEventListener("click", function () {
uploadedFiles.splice(i, 1);
createElements();
});
}
}
Submit to server:
let fd = new FormData();
uploadedFiles.forEach((f, i) => {
fd.append("files[]", f);
});
fetch("yourEndpoint", {
method: "POST",
body: fd,
headers: {
//do not set Content-Type
}
}).then(...)
I mix the solutions of many developers and reach to this solution.
It change the original array list after deletion which means if we want to save the images then we can do so.
<script>
var images = [];
function image_select() {
var image = document.getElementById('image').files;
for (i = 0; i < image.length; i++) {
images.push({
"name" : image[i].name,
"url" : URL.createObjectURL(image[i]),
"file" : image[i],
})
}
document.getElementById('container').innerHTML = image_show();
}
function image_show() {
var image = "";
images.forEach((i) => {
image += `<div class="image_container d-flex justify-content-center position-relative">
<img src="`+ i.url +`" alt="Image">
<span class="position-absolute" onclick="delete_image(`+ images.indexOf(i) +`)">×</span>
</div>`;
})
return image;
}
function delete_image(e) {
images.splice(e, 1);
document.getElementById('container').innerHTML = image_show();
const dt = new DataTransfer()
const input = document.getElementById('image')
const { files } = input
for (let i = 0; i < files.length; i++) {
const file = files[i]
if (e !== i)
dt.items.add(file);
}
input.files = dt.files;
console.log(document.getElementById('image').files);
}
</script>
*******This is html code ******
<body>
<div class="container mt-3 w-100">
<div class="card shadow-sm w-100">
<div class="card-header d-flex justify-content-between">
<h4>Preview Multiple Images</h4>
<form class="form" action="{{route('store')}}" method="post" id="form" enctype="multipart/form-data">
#csrf
<input type="file" name="image[]" id="image" multiple onchange="image_select()">
<button class="btn btn-sm btn-primary" type="submit">Submit</button>
</form>
</div>
<div class="card-body d-flex flex-wrap justify-content-start" id="container">
</div>
</div>
</div>
</body>
******* This is CSS code ********
<style>
.image_container {
height: 120px;
width: 200px;
border-radius: 6px;
overflow: hidden;
margin: 10px;
}
.image_container img {
height: 100%;
width: auto;
object-fit: cover;
}
.image_container span {
top: -6px;
right: 8px;
color: red;
font-size: 28px;
font-weight: normal;
cursor: pointer;
}
</style>
You may wish to create an array and use that instead of the read-only filelist.
var myReadWriteList = new Array();
// user selects files later...
// then as soon as convenient...
myReadWriteList = FileListReadOnly;
After that point do your uploading against your list instead of the built in list. I am not sure of the context you are working in but I am working with a jquery plugin I found and what I had to do was take the plugin's source and put it in the page using <script> tags. Then above the source I added my array so that it can act as a global variable and the plugin could reference it.
Then it was just a matter of swapping out the references.
I think this would allow you to also add drag & drop as again, if the built in list is read-only then how else could you get the dropped files into the list?
:))
I solve it this way
//position -> the position of the file you need to delete
this.fileImgs.forEach((item, index, object) => {
if(item.idColor === idC){
if(item.imgs.length === 1){
object.splice(index,1) }
else{
const itemFileImgs = [...item.imgs];
itemFileImgs.splice(position,1)
item.imgs = [...itemFileImgs]
}
}});
console.log(this.fileImgs)
In vue js :
self.$refs.inputFile.value = ''
I just change the type of input to the text and back to the file :D