I have the following 2 functions:
$(document).on(`dragover drop change`, `.fileUpload`, async function(e) {
e.stopPropagation();
e.preventDefault();
const thisEl = $(this);
if (thisEl.val() === `` || e.type === `dragover`) {
return;
}
const headerRec = await getCSVHeaderRow(thisEl[ 0 ].files[ 0 ], `,`);
console.log(`header rec: `, headerRec,headerRec2);
});
function getCSVHeaderRow(file, delim) {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.onload = function(e) {
let headerRow = [];
const rows = e.target.result.split(`\r\n`);
headerRow = rows[ 0 ];
console.log(`headerrow: `, headerRow, headerRow.split(delim));
resolve(headerRow.split(delim));
};
reader.onerror = reject;
reader.readAsText(file);
});
}
I am trying to use $.when() rather than await but the called function returns
a promise object rather than the array:
$(document).on(`dragover drop change`, `.fileUpload`, function(e) {
e.stopPropagation();
e.preventDefault();
const thisEl = $(this);
if (thisEl.val() === `` || e.type === `dragover`) {
return;
}
const headerRec2 = $.when(getCSVHeaderRow2(thisEl[ 0 ].files[ 0 ], `,`)).then(function(data) {
return data;
});
console.log(`header rec: `, headerRec,headerRec2);
});
function getCSVHeaderRow2(file, delim) {
const dfr = $.Deferred();
let reader = new FileReader();
reader.onload=function(e) {
let headerRow = [];
const rows = e.target.result.split(`\r\n`);
headerRow = rows[ 0 ];
console.log(`headerrow: `, headerRow, headerRow.split(delim));
return headerRow.split(delim);
};
return dfr.resolve(reader.readAsText(file));
}
What do I need to change in getCSVHeaderRow2() to have it return the array in the way
getCSVHeaderRow() is?
Related
I know this question has been asked but none of the solutions are working for me and I can't figure out what's wrong. I have an object with a nested array of objects I am stringifying but I get a blank array of the nested array when I use JSON.stringify on it.
This is a simplified version of the way I'm constructing the object. The main difference is that there is a for loop iterating through all the rows, here I am just manually creating 2 rows
// JAVASCRIPT
let obj = {};
obj['type'] = 'Setting';
obj['id'] = 1;
obj['import'] = parseCSV();
function parseCSV() {
let jsonData = [];
let row1 = {};
let row2 = {};
row1['date'] = '2022-01-01';
row1['amount'] = '30';
row2['date'] = '2022-01-02';
row2['amount'] = '50';
jsonData.push(row1);
jsonData.push(row2);
return jsonData;
}
console.log('RAW DATA', obj);
console.log('STRINGIFIED', JSON.stringify(obj));
The above outputs the correct stringified JSON
But the full version of my code gives me a blank array for import.
Both objects look identical to me. The culprit is somewhere in my parseCSV function, because when I use the simplified version above I get the correct stringified data, but I can't pinpoint where I'm wrong. Below is my full function.
function parseCSV(file) {
let filename = file.name;
let extension = filename.substring(filename.lastIndexOf('.')).toUpperCase();
if(extension == '.CSV') {
try {
let reader = new FileReader();
let jsonData = [];
let headers = [];
reader.readAsBinaryString(file);
reader.onload = function(e) {
let rows = e.target.result.split('\n');
for(let i = 0; i < rows.length; i++) {
let cells = rows[i].split(',');
let rowData = {};
for(let j = 0; j < cells.length; j++) {
if(i == 0) headers.push(cells[j].trim());
else {
if(headers[j]) rowData[headers[j]] = cells[j].trim();
}
}
if(i != 0 && rowData['date'] != '') jsonData.push(rowData);
}
}
return jsonData;
} catch(err) {
console.error('!! ERROR READING CSV FILE', err);
}
} else alert('PLEASE UPLOAD A VALID CSV FILE');*/
}
Thanks for the help!
EDIT
When I add await before parseCSV as #BJRINT's answer suggests I get a syntax error await is only valid in async function
async function submitForm(event) {
event.preventDefault();
let newItem = await gatherFormData(event.target);
return fetch('server.php', {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(newItem)
})
.then(checkError)
.then(data => parseData(data))
.catch(err => console.error('>> ERROR READING JSON DATA', err));
}
function gatherFormData(target) {
const inputs = target.querySelectorAll('input');
let obj = {};
inputs.forEach(function(input) {
if(intKeys.indexOf(input.name) >= 0) obj[input.name] = parseInt(input.value);
else if(curKeys.indexOf(input.name) >= 0) obj[input.name] = parseInt(parseFloat(input.value) * 100);
else if(chkKeys.indexOf(input.name) >= 0) input.checked ? obj[input.name] = 1 : obj[input.name] = 0;
else if(fileKeys.indexOf(input.name) >= 0 && input.files.length > 0) obj[input.name] = parseCSV(input.files[0]);
else obj[input.name] = input.value;
});
return obj;
}
The problem does not come from the stringify function. Since you are filling your array asynchronously (when the reader callback is executed) but returning your data first, it is empty.
You could wrap your function with a Promise that resolves when the reader callback function is finally executed, like so:
function parseCSV(file) {
return new Promise((resolve, reject) => {
let filename = file.name;
let extension = filename.substring(filename.lastIndexOf('.')).toUpperCase();
if(extension !== '.CSV')
return reject('PLEASE UPLOAD A VALID CSV FILE')
try {
let reader = new FileReader();
let jsonData = [];
let headers = [];
reader.readAsBinaryString(file);
reader.onload = function(e) {
let rows = e.target.result.split('\n');
for(let i = 0; i < rows.length; i++) {
let cells = rows[i].split(',');
let rowData = {};
for(let j = 0; j < cells.length; j++) {
if(i == 0) headers.push(cells[j].trim());
else {
if(headers[j]) rowData[headers[j]] = cells[j].trim();
}
}
if(i != 0 && rowData['date'] != '') jsonData.push(rowData);
}
return resolve(jsonData);
}
} catch(err) {
return reject('!! ERROR READING CSV FILE', err);
}
})
}
// calling the function
const data = await parseCSV(file)
The solution that worked for my specific case was to use a combination of BJRINT's answer and a timer to keep checking if the data had finished loading which I found here.
async function parseCSV(file) {
return await new Promise((resolve, reject) => {
let extension = file.name.substring(file.name.lastIndexOf('.')).toUpperCase();
if(extension !== '.CSV') reject('PLEASE UPLOAD A VALID CSV FILE');
try {
let reader = new FileReader();
reader.readAsText(file);
reader.onload = function(e) {
let jsonData = [];
let headers = [];
let rows = e.target.result.split(/\r\n|\r|\n/);
for(let i = 0; i < rows.length; i++) {
let cells = rows[i].split(',');
let rowData = {};
for(let j = 0; j < cells.length; j++) {
if(i == 0) headers.push(cells[j].trim());
else {
if(headers[j]) rowData[headers[j]] = cells[j].trim();
}
}
if(i != 0 && rowData['date'] != '') jsonData.push(rowData);
}
resolve(jsonData);
}
} catch(err) {
reject(err);
}
});
}
function submitForm(event) {
event.preventDefault();
showForm(false);
loading.classList.remove('hidden');
let ready = true;
const inputs = event.target.querySelectorAll('input');
let newItem = {};
let check = function() {
if(ready === true) {
console.log(newItem);
console.log(JSON.stringify(newItem));
return fetch('server.php', {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(newItem)
})
.then(checkError)
.then(data => parseData(data))
.catch(err => console.error('>> ERROR READING JSON DATA', err));
}
setTimeout(check, 1000);
}
inputs.forEach(function(input) {
if(intKeys.indexOf(input.name) >= 0) newItem[input.name] = parseInt(input.value);
else if(curKeys.indexOf(input.name) >= 0) newItem[input.name] = parseInt(parseFloat(input.value) * 100);
else if(chkKeys.indexOf(input.name) >= 0) input.checked ? newItem[input.name] = 1 : newItem[input.name] = 0;
else if(fileKeys.indexOf(input.name) >= 0 && input.files.length > 0) {
ready = false;
parseCSV(input.files[0]).then(data => {
ready = true;
newItem[input.name] = data;
});
}
else newItem[input.name] = input.value;
});
check();
}
I was struggling with a file upload I want to save via a PUT request. I updated the question with the solution.
I have a form that allows for multiple images to be uploaded in different fields, but also other fields with strings:
I loop over all fields in order to collect the information in a JSON, then push them towards my API:
keys is a dict with the names of the keys for the JSON (API checks for those keys), for example: name|STR, price|FLOAT, ppic|IMG.
$('#checkButton').on('click', function( event ) {
let form = document.querySelector('#productForm');
let nbr = document.querySelector('#productdata').getAttribute("pid")
let return_dict = {"number": nbr};
const pendings = ["name", "pic"].map(async function ( key ) {
return_dict["name"] = form.elements[0].value;
return_dict["pic"] = await img_2_b64(form.elements[1]);
});
Promise.all(pendings).then((values) => {
write_2_DB_with_ajax_call ( return_dict )
});
});
If the key identifies the field as IMG I want to convert it to base64 and save it - this is my solution:
function img_2_b64( element ) {
return new Promise((resolve, reject) => {
let file = element.files[0];
let reader = new FileReader();
reader.onloadend = function(e) {
resolve(e.target.result);
};
reader.onerror = function() {
reject();
};
reader.readAsDataURL(file);
});
Maybe you can use promises to have more control over the information flow:
$('#checkButton').on('click', function( event ) {
let form = document.querySelector('#productForm');
let return_dict = {"number": 0, "data": d};
const pendings = keys.map(async function ( key ) {
for ( let fe = 0; fe < form.elements.length; fe++ ) {
if ( form.elements[fe].getAttribute("data-key") === key ) {
if ( key.split("|")[1] === "IMG" ) {
d[key] = await img_2_b64(form.elements[fe]);
} else {
d[key] = form.elements[fe].value;
}
};
};
});
Promise.all(pendings)
.then((values) => {
write_2_DB_with_ajax_call ( values )
});
});
and
function img_2_b64( element ) {
return new Promise((resolve, reject) => {
let fileprops = "";
let file = element.files[0];
let reader = new FileReader();
reader.onloadend = function() {
resolve(reader.readAsDataURL(file));
};
reader.onerror = function() {
reject();
};
})
}
I wrote following function for loading indexeddb. (from IndexedDB 備忘メモ)
I think this function should return Array of object. But, sometimes it returns an object. What are the possibilities of bug ?
Chrome developer tool said type of object was Array during in "load" function. But, after received "records" is type of object.
async function load(dbobj, db, index, range) {
return new Promise(async (resolve, reject) => {
const saves = [];
const req = db.transaction(dbobj.storeName).objectStore(dbobj.storeName).index(index).openCursor(range);
const onfinished = () => {
console.log(`${saves.length} saves found.`);
if (saves.length > 0) {
resolve(saves[saves.length - 1]);
}
};
req.onerror = reject;
req.onsuccess = (ev) => {
const cur = ev.target.result;
if (cur) {
saves.push(cur.value);
cur.continue();
} else {
onfinished();
}
};
});
}
// example of receiving data
var records = await load(dbobj, db, index, range);
you are resolving only the value at the last index! resolve(saves) if you need the entire array;
async function load(dbobj, db, index, range) {
return new Promise(async (resolve, reject) => {
const saves = [];
const req = db.transaction(dbobj.storeName).objectStore(dbobj.storeName).index(index).openCursor(range);
const onfinished = () => {
console.log(`${saves.length} saves found.`);
if (saves.length > 0) {
resolve(saves); // you are resolving only the value at the last index! resolve(saves) if you need the entire array;
}
};
req.onerror = reject;
req.onsuccess = (ev) => {
const cur = ev.target.result;
if (cur) {
saves.push(cur.value);
cur.continue();
} else {
onfinished();
}
};
});
}
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
I'm trying to use FileReader() in my website but it's returning undefined in the array. This is the first time I'm using the FileReader to send encoded data from my input file. I'm using the API of Recruiterbox and as you can see in the print the encoded_data is undefined. I'm stucked here and need some help.
applyOpening: function applyOpening() {
let fields = [];
let formControl = document.querySelectorAll('.form-control');
for (var i = 0; i < formControl.length; i++) {
let field = formControl[i];
let obj = {
key: field.getAttribute('name')
}
if (field.tagName.toLowerCase() === 'input' && field.getAttribute('type') === 'file' ) {
obj.value = {
'encoded_data': getBase64(field.files[0]),
'file_name': field.value
}
} else {
obj.value = field.value
}
fields.push(obj);
}
function getBase64(file) {
let reader = new FileReader();
if (file) {
return reader.readAsDataURL(file);
} else {
return false;
}
reader.onloadend = function() {
return reader.result;
};
}
$.ajax({
url: 'https://jsapi.recruiterbox.com/v1/openings/' + id + '/apply?client_name=clientname',
data: JSON.stringify({ fields: fields }),
dataType: 'json',
contentType: 'application/json',
type: 'POST',
success: function(response) {
console.log(JSON.stringify(data));
},
error: function(er) {
console.error(er);
}
});
}
Update:
applyOpening: function applyOpening() {
function getBase64(file) {
return new Promise(function(resolve) {
var reader = new FileReader();
reader.onloadend = function() {
resolve(reader);
}
reader.readAsDataURL(file);
})
}
let fields = [];
let formControl = document.querySelectorAll('.form-control');
for (var i = 0; i < formControl.length; i++) {
let field = formControl[i];
let obj = {
key: field.getAttribute('name')
}
if (field.tagName.toLowerCase() === 'input' && field.getAttribute('type') === 'file' ) {
if (field.files[0]) {
getBase64(field.files[0]).then(function(reader) {
obj.value = {
'encoded_data': reader.result,
'file_name': field.value.replace("C:\\fakepath\\", "")
};
});
}
} else {
obj.value = field.value;
}
if (obj.key !== null) {
fields.push(obj);
}
console.log(obj);
}
app.postApplyOpening(fields);
Console log:
Request Payload:
Update 2:
applyOpening: async function applyOpening() {
function getBase64(file) {
return new Promise(function(resolve) {
var reader = new FileReader();
reader.onloadend = function() {
resolve(reader);
}
reader.readAsDataURL(file);
})
}
let fields = [];
let formControl = document.querySelectorAll('.form-control');
for (var i = 0; i < formControl.length; i++) {
let field = formControl[i];
let obj = {
key: field.getAttribute('name')
}
if (field.tagName.toLowerCase() === 'input' && field.getAttribute('type') === 'file' ) {
if (field.files[0]) {
let reader = await getBase64(field.files[0]);
obj.value = {
'encoded_data': reader.result.replace(new RegExp("^data:[A-Z]+/[A-Z]+;base64,", "gi"), ''),
'file_name': field.value.replace("C:\\fakepath\\", "")
};
}
} else {
obj.value = field.value;
}
if (obj.key !== null) {
fields.push(obj);
}
console.log(obj);
}
app.postApplyOpening(fields);
},
FileReader returns results asynchronously. Use a promise to get the result asynchronously. You cannot return the result of readAsDataURL, that is undefined, which is what you are seeing.
See here: Javascript base64 encoding function returns undefined
I finished the functionality side of this simple app but now I want to add some good UX aswell so I want to add a loading animation (a spinner) while the JSON loads and before it displays the result of the promise, but I cannot seem to find a solution by googling.
Here is the pen: https://codepen.io/kresimircoko/pen/ZLJjVM.
Here is the JavaScript code:
const API_KEY = '?api_key=625023d7336dd01a98098c0b68daab7e';
const root = 'https://www.warcraftlogs.com:443/v1/';
const zonesBtn = document.querySelector('#zones');
const responseList = document.querySelector('#response');
console.clear();
const requestJSON = objType => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function() {
try {
resolve(JSON.parse(this.responseText));
}
catch (e) {
reject(e);
}
};
xhr.onerror = reject;
xhr.open('GET', root + objType + API_KEY);
xhr.send();
});
};
function displayBosses(zoneID) {
let bosses = document.querySelectorAll(`.bosses[data-zoneid="${zoneID}"]`);
requestJSON('zones')
.then(data => {
let output = '';
data.find(zone =>
zone.id === parseInt(zoneID, 10)
).encounters.map(encounter => {
output += `<li class="boss" data-zoneid="${zoneID}">${encounter.name}</li>`;
bosses.forEach(boss => {
boss.innerHTML = output;
});
}).join('');
});
}
function displayZones() {
let output = '';
requestJSON('zones')
.then(zones => {
return zones.map(zone => {
output += `
<ul data-zoneid="${zone.id}" class="zones">
<span>${zone.name}</span>
<ul data-zoneid="${zone.id}" class="bosses"></ul>
</ul>`;
response.innerHTML = output;
}).join('');
})
.then(responseList.style.display = 'flex');
}
zonesBtn.addEventListener('click', displayZones);
responseList.addEventListener('click', evt => {
const target = evt.target.parentElement;
const zoneID = target.dataset.zoneid;
displayBosses(zoneID);
if (target.classList.contains('clicked'))
target.classList.remove('clicked');
else
target.classList.add('clicked')
});
The spinner is a FontAwesome icon wrapped in a spinner div for which we control the display property to show up when the button is clicked but hide when the promise has resolved.
function displayZones() {
if (!this.classList.contains('open')) {
spinner.style.display = 'block';
this.classList.add('open');
}
let output = '';
requestJSON('zones')
.then(zones => {
spinner.style.display = 'none';
return zones.map(zone => {
output += `
<ul data-zoneid="${zone.id}" class="zones">
<span>${zone.name}</span>
<ul data-zoneid="${zone.id}" class="bosses"></ul>
</ul>`;
response.innerHTML = output;
}).join('');
})
.then(responseList.style.display = 'flex');
}