How to make am array immutable - javascript

I am using the custom function option for downloading a CSV usingMaterialTable. In the function I am modifying the data of only three columns.
When exportCsv is executed then the data array will contain the last changes which will results on a wrong output.
const downloadCsv = (data, fileName) => {
const finalFileName = `${fileName}.csv`;
const a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([data], { type: "text/csv" }));
a.setAttribute("download", finalFileName);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
export default function ReTable(props) {
const resultsData = useSelector((state) => state.results.data);
return (
<div>
<MaterialTable
columns={columns}
data={resultsData}
options={{
........
.......
.....
exportCsv: (columns, data) => {
const csvData = [...resultsData];
csvData.forEach(item => {
item.account = '="' + item.account + '"';
item.zip4 = '="' + item.zip4 + '"';
item.zip5 = '="' + item.zip5 + '"';
});
const dataRows = csvData.map(({ tableData, ...row }) => Object.values(row));
const csvContent = [header, ...dataRows].map(e => e.join(',')).join("\n");
downloadCsv(csvContent, props.name);
},
I don’t want to change the data so I have created a new csvData but apparently its it is effecting the data.
I am not sure what I am doing wrong ? I need to update the columns only ones.
Thank you

You're just doing a shallow copy of the array when you call [...resultsData]. Either use a library that will do a deep copy, find one of the many SO answers that will provide a solution, or do the work of converting values when you call map instead of worrying about making the array immutable:
exportCsv: (columns, data) => {
const colsToTransform = ['account', 'zip4', 'zip5'];
const dataRows = resultsData.map(x => Object.entries(x)
.map(kvp => (colsToTransform.includes(kvp[0])) ? '="' + kvp[1] + '"' : kvp[1]));
const csvContent = [header, ...dataRows].map(e => e.join(',')).join("\n");
downloadCsv(csvContent, props.name);
}

Related

Foreach for array

I have an array which I populate like so
var list = [];
featureLayer.queryFeatures(querySnTR)
.then((result) => {
result.attachmentInfos.forEach((x) => {
list.push(uriString + "/" + x.id);
});
});
console.log("list", list);
I print out the list with console.log and it returns values inside.
Afterwards I do a foreach to go through all the elements inside and create a div for each of them. The thing is, it doesn't even go in the foreach function.
list.forEach((x) => {
console.log("CL", list);
console.log("x element", x);
var image = document.createElement("img");
image.src = x;
image.className = "queryImg";
document.getElementById("queryResults").appendChild(image);
});
It doesn't print out CL or x element for that matter.
Any ideas as to why?
The whole original code, for reference
startup: function () {
var _that = this;
_this = _that;
this.map.on("click", function (e) {
_this.map.graphics.clear();
identifyTask = new IdentifyTask("https://server/arcgis/rest/services/MUNICIPALITY_BUNDLE/ZK_KATASTAR_NA_ZELENILO/MapServer");
identifyParams = new IdentifyParameters();
identifyParams.tolerance = 10;
identifyParams.returnGeometry = true;
identifyParams.layerIds = [1];
identifyParams.layerOption = IdentifyParameters.LAYER_OPTION_ALL;
identifyParams.width = _this.map.width;
identifyParams.height = _this.map.height;
identifyParams.spatialReference = _this.map.spatialReference;
identifyParams.geometry = e.mapPoint;
identifyParams.mapExtent = _this.map.extent;
identifyTask.execute(identifyParams).then(function (data) {
objId = data[0].feature.attributes.objectid;
const querySnTR = {
where: "1 = 1",
outFields: ["*"]
};
var uriString = "https://server/arcgis/rest/services/MUNICIPALITY_BUNDLE/ZK_KATASTAR_NA_ZELENILO/MapServer/101/" + objId + "/attachments";
var featureLayer = new esri.layers.FeatureLayer(uriString);
featureLayer.queryFeatures(querySnTR)
.then((result) => {
result.attachmentInfos.forEach((x) => {
list.push(uriString + "/" + x.id);
});
});
const myFunction = async () => {
const { attachmentInfos } = await featureLayer.queryFeatures(querySnTR);
const list = attachmentInfos.map(({ id }) => `${uriString}/${id}`);
console.log("list", list);
list.forEach((x) => {
var image = document.createElement("img");
image.src = x;
image.className = "queryImg";
document.getElementById("queryResults").appendChild(image);
});
};
});
});
}
That's a trick on how the console works.
When you are executing the log the list is empty (100% sure) because you are populating it asynchronously. But the console has the reference to it and it will print it afterwards.
That's why your list is empty. You need to handle asynchrony here. You could work with an async/await approach or using promises, that will depend on the rest of your code, this is an example of how to do it with an async function (and rewritted it to modern javascript):
const myFunction = async () => {
const {attachmentInfos} = await featureLayer.queryFeatures(querySnTR);
const list = attachmentInfos.map(({id}) => `${uriString}/${id}`);
console.log("list", list);
list.forEach((x) => {
// put your code here
});
};
Edited:
Now that you share all your code you can simply do:
featureLayer.queryFeatures(querySnTR)
.then((result) => {
result.attachmentInfos.forEach((attachmentInfo) => {
var x = uriString + "/" + attachmentInfo.id
var image = document.createElement("img");
image.src = x;
image.className = "queryImg";
document.getElementById("queryResults").appendChild(image);
});
});
I would recommend you also to give vars meaningful names, not x but attachmentInfo, etc...

Node JS - Loop throught JSON and map values

I have a JSON like this:
{
"generic": {
"tables": {
"header": "Header",
"columns": "Columns"
},
"yes": "Yes",
"no": "No"
}
}
But with thousands of lines and more nested objects. I need to translate all this strings, so I'm making a script to do it. How can I loop through each string and replace it with something?
I've searched and found this thread: Looping through JSON with node.js but I can't find any solution that fits my needs.
I made this quick script:
const fs = require('fs');
const obj = JSON.parse(fs.readFileSync('en.json', 'utf-8'));
const translate = (obj, path = '') => {
const keys = Object.keys(obj);
keys.forEach((key) => {
const type = typeof obj[key];
if (type === 'object') {
translate(obj[key], path === '' ? key : path + '.' + key);
} else {
console.log(path + ' --> [' + key + ']: ' + obj[key]);
}
});
};
translate(obj);
It loops through the array. In the console log line I have the full path of the translation item (i.e.: 'generic.tables.header') and I have the key and value of the translation.
How can i make a new object containing the keys ?
I think I found a way, instead of saving the path, create an empty object and insert the keys there:
const fs = require('fs');
const obj = JSON.parse(fs.readFileSync('en.json', 'utf-8'));
const newObj = {};
const translate = (obj, path = '', objVal) => {
const keys = Object.keys(obj);
keys.forEach((key) => {
const type = typeof obj[key];
if (type === 'object') {
objVal[key] = {};
const newObjVal = objVal[key];
translate(obj[key], path === '' ? key : path + '.' + key, newObjVal);
} else {
objVal[key] = obj[key];
//console.log(path + ' --> [' + key + ']: ' + obj[key]);
}
});
};
translate(obj, '', newObj);

JSON values not displaying when outputting multiple values (array) to HTML

I am iterating over a JSON object via fetch().
I would like to access one of the objects within the objects and iterated over it accessing the key: value pairs and output the values to HTML via list items.
When I try to output the values I only get the last value of the powerstats (there are 6)
What do I need to change here to have all values display properly on my page?
I tried to create a for loop and iterate over the value.length, however value.length gives me 2 as an answer.
Feel free to use the provided api key.
function get_hero(rand_number) {
const api_key = '10156555926000957';
let hero_id = rand_number;
let hero_url = `https://www.superheroapi.com/api/${api_key}/${hero_id}`;
fetch(hero_url)
.then(res => {
return res.json();
})
.then( data => {
let ps = data.powerstats;
Object.entries(ps).forEach(([key, value]) => {
console.log(key + ' - ' + value) // key - value
console.log(value.length)
const smt = `<ul>
<li>${value}</li>
<li>${value}</li>
<li>${value}</li>
<li>${value}</li>
<li>${value}</li>
<li>${value}</li>
</ul>`;
const power_stats = document.getElementById('powerstats');
power_stats.innerHTML = smt;
})
})
.catch(function() {
console.log('error')
})
}
Sry didn't had coffee yet, XD
so your problem is that you replace all of the power_stats.innerHTML = smt; with smt over and over again. You want to use element.appendChild(element) to ADD to a list. Not overwrite
.then(data => {
const ps = data.powerstats;
const power_stats = document.getElementById('powerstats');
const list = document.createElement(`ul`)
power_stats.appendChild(list)
Object.entries(ps).forEach(([key, value]) => {
console.log(key + ' - ' + value) // key - value
console.log(value.length)
const smt = document.createElement(`li`)
smt.innerText = `The heroes ${key} is ${value}`
list.appendChild(smt)
})
})
Currently you function return void, so you don't wait for fetch, so you should return promise:
function get_hero(rand_number) {
const api_key = '10156555926000957';
let hero_id = rand_number;
let hero_url = `https://www.superheroapi.com/api/${api_key}/${hero_id}`;
return fetch(hero_url)
.then(res => {
return res.json();
})
.then( data => {
let ps = data.powerstats;
Object.entries(ps).forEach(([key, value]) => {
console.log(key + ' - ' + value) // key - value
console.log(value.length)
const smt = `<ul>
<li>${value}</li>
</ul>`;
const power_stats = document.getElementById('powerstats');
power_stats.innerHTML = smt;
})
})
.catch(function() {
console.log('error')
})
}
Using:
get_hero(123).then(() => console.log('done!'));
(for me https://www.superheroapi.com/ is not responding, so maybe it is not the only problem)

Nodejs Scraper isn't moving to next page(s)

Hey guys this is a follow on from my other question, i have created a Nodejs Scraper that doesnt seem to want to go through the pages, it stays on the first. my source code is below
const rp = require('request-promise');
const request = require('request');
const otcsv = require('objects-to-csv');
const cheerio = require('cheerio');
//URL To scrape
const baseURL = 'xxx';
const searchURL = 'xxxx';
//scrape info
const getCompanies = async () => {
// Pagination test
for (let index = 1; index <= 20; index = index + 1) {
const html = await rp.get(baseURL + searchURL + index);
const $ = await cheerio.load(html);
console.log("Loading Pages....");
console.log("At page number " + index);
// end pagination test
//const htmls = await rp(baseURL + searchURL);
const businessMap = cheerio('a.business-name', html).map(async (i, e) => {
const link = baseURL + e.attribs.href;
const innerHtml = await rp(link);
const emailAddress = cheerio('a.email-business', innerHtml).prop('href');
const name = e.children[0].data || cheerio('h1', innerHtml).text();
const phone = cheerio('p.phone', innerHtml).text();
return {
// link,
name,
emailAddress: emailAddress ? emailAddress.replace('mailto:', '') : '',
phone,
}
}).get();
return Promise.all(businessMap);
}
};
console.log("Finished Scraping.... Now Saving!")
//save to CSV
getCompanies()
.then(result => {
const transformed = new otcsv(result);
return transformed.toDisk('./output.csv');
})
.then(() => console.log('Scrape Complete :D '));
As you can see I have tried a few different ways to make this happen so any help will be gratefully appreciated.

How to get specific data from API and use in function?

I'm trying to build a weather app in nodejs with dark-sky API. I got a separate js file and keep my forecast info in a callback function. However, I also want to use Skycons for visualization.
this is my forecast.js. in that script I get info like temperature etc. so I need to get "icon" data as well
const request = require('request')
const getWeather = (latitude, longitude, callback) => {
const url = 'https://api.darksky.net/forecast/b0854aec02e1655c7203e05c7d77dfd1/' + latitude + ',' + longitude + '/?units=si'
request({
url: url,
json: true
}, (error, {
body /* "response " evezine response object icindeki "body" birbasa daxil edirem function-a*/
}) => {
if (error) {
callback('Unable to connect to weather service!', undefined)
} else if (body.error) {
callback('Unable to find location'.undefined)
} else {
callback(undefined,
'It is currently ' + body.currently.temperature + '°C out in ' + body.timezone + '. Weather ' + body.daily.data[0].summary + ' There is a ' + (body.currently.precipProbability * 100) + '% chance of rain.'
)
}
})
}
module.exports = getWeather
This is the fetch function, and I tried to invoke and activate Skycons in this function. but I cannot get "icon" data from API.
const weatherForm = document.querySelector("form");
const search = document.querySelector("input");
const messageOne = document.querySelector("#message-1");
const messageTwo = document.querySelector("#message-2");
const skycons = new Skycons({
color: '#222'
})
skycons.set('icon', 'clear-day');
skycons.play();
const icon = data.forecast.icon;
weatherForm.addEventListener("submit", e => {
e.preventDefault();
const location = search.value;
messageOne.textContent = "Please wait....";
messageTwo.textContent = "";
fetch(
"http://localhost:4000/weather?address=" + encodeURIComponent(location)
).then(response => {
response.json().then(data => {
if (data.error) {
messageOne.textContent = data.error;
} else {
messageOne.textContent = data.location;
messageTwo.textContent = data.forecast;
}
currentSkycons(icon, document.getElementById('icon'));
});
});
messageOne.textContent;
messageTwo.textContent;
});
function currentSkycons(icon, iconID) {
const currentIcon = icon.replace(/-/g, "_").toUppercase();
skycons.play();
return skycons.set(iconID, Skycons[currentIcon]);
}
but to use Skycons, I need to get "icon" from the dark-sky API. how I can get this data aside from my forecast js? To get and assign that data to a variable and use in another function
It looks like the data object is only accessible in the response json, which means you would need to access forcast.icon when you have the response.

Categories

Resources