Using the query string URLSearchparams and consolidating my fetch requests into the Promise.all method, my code is now able to print the elements that I want in the console.log. However, I'm having a hard time getting that information to display in html. It returns "undefined" on the screen but the console.log shows the correct data. What's missing ?
async function loadData() {
const queryStr_id = window.location.search;
console.log(queryStr_id);
const product = new URLSearchParams(queryStr_id);
console.log(product);
const id = product.get("_id");
console.log(id);
try {
const url_teddies = "http://localhost:3000/api/teddies/" + id;
const url_cameras = "http://localhost:3000/api/cameras/" + id;
const url_furniture = "http://localhost:3000/api/furniture/" + id;
const results = await Promise.all([fetch(url_teddies), fetch(url_cameras), fetch(url_furniture)]);
const dataPromises = results.map((result) => result.json());
const finalData = await Promise.all(dataPromises);
console.log(finalData);
for (i = 0; i < finalData.length; i++) {
const prodImg = finalData[i].imageUrl;
const prodName = finalData[i].name;
const prodPrice = finalData[i].price / 100 + " Eur";
const prodDesc = finalData[i].description;
const coloris = finalData[i].colors;
const lenses = finalData[i].lenses;
const varnish = finalData[i].varnish;
console.log(prodImg);
console.log(prodName);
console.log(prodPrice);
console.log(prodDesc);
console.log(coloris);
console.log(lenses);
console.log(varnish);
const productDetails = `
<div class="produit__card__wrapper">
<div class="produit__card__content">
<img src="${finalData[i].imageUrl}" alt="${finalData[i].name}" class="productImg"></img>
<div class="container_text">
<h1 class="name"><span>${finalData[i].name}</span></h1>
<p class="price"><strong>Price : </strong><span>${finalData[i].price / 100 + " €"}</span></p>
<p class="description"><strong>Description : </strong><span>${finalData[i].description}</span></p>
<form>
<label for="product_color"></label>
<select name="product_color" id="product_color">
</select>
</form>
<button id="addToCart" type="submit " name="addToCart">Ajouter au panier</button>
</div>
</div>
</div>
`;
const container = document.querySelector(".container");
container.innerHTML = productDetails;
}
return finalData;
} catch (err) {
console.log(err);
}
}
loadData()
I think you just forgot to flatten your three promised arrays before assigning them to finalData. Try
const finalData = (await Promise.all(dataPromises)).reduce((data, arr) => data.concat(arr), []);
Related
`
if (sInd === dInd) {
const reorderCardItems = reorderCard(items[sInd], source.index, destination.index);
const newState = [...items];
newState[sInd].card = reorderCardItems;
seState({newState: newState});
`
if (sInd === dInd) {
const reorderCardItems = reorderCard(items[sInd], source.index, destination.index);
let newState = [...items];
newState[sInd].card = reorderCardItems;
newState= newState;
it's giving me error that 'newState' is assigned to itself.
I have multiple files in my apps script project. Some of them are library files that provide utility functions for a larger app. How can I import them into my main file?
For example, in Node, I would be able to import update-cell-1.gs and update-cell-2.gs into my main.gs file like this:
// update-cell-1.gs
export default function() {
// code to update cell 1
}
// update-cell-2.gs
export default function() {
// code to udpate cell 2
}
// main.gs
import updateCell1 from "update-cell-1.gs";
import updateCell2 from "update-cell-2.gs";
function main() {
updateCell1();
updateCell2();
}
main();
What is the equivalent in apps script?
When I try using module.exports, I get this error:
ReferenceError: module is not defined
When I try using export default, I get this error:
SyntaxError: Unexpected token 'export'
Unlike node.js or js web frameworks, You can call any function in any file in the same script project directly without exporting or importing anything. However, the order of the files matter.
Here's a script that backs up all of the files in a script project.
You can choose either separate files or all one JSON file. You can also select which files that you wish to save.
GS:
function saveScriptBackupsDialog() {
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutputFromFile('backupscripts1'), 'Script Files Backup Dialog');
}
function scriptFilesBackup(obj) {
console.log(JSON.stringify(obj));
const scriptId = obj.script.trim();
const folderId = obj.folder.trim();
const saveJson = obj.saveJson;
const saveFiles = obj.saveFiles;
const fA = obj.selected;
if (scriptId && folderId) {
const base = "https://script.googleapis.com/v1/projects/"
const url1 = base + scriptId + "/content";
const url2 = base + scriptId;
const options = { "method": "get", "muteHttpExceptions": true, "headers": { "Authorization": "Bearer " + ScriptApp.getOAuthToken() } };
const res1 = UrlFetchApp.fetch(url1, options);
const data1 = JSON.parse(res1.getContentText());
const files = data1.files;
const folder = DriveApp.getFolderById(folderId);
const res2 = UrlFetchApp.fetch(url2, options);
const data2 = JSON.parse(res2.getContentText());
let dts = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyyMMdd-HH:mm:ss");
let subFolderName = Utilities.formatString('%s-%s', data2.title, dts);
let subFolder = folder.createFolder(subFolderName);
if (saveFiles) {
files.forEach(file => {
if (file.source && file.name) {
let ext = (file.type == "HTML") ? ".html" : ".gs";
if (~fA.indexOf(file.name)) {
subFolder.createFile(file.name + ext, file.source, MimeType.PLAIN_TEXT)
}
}
});
}
if (saveJson) {
subFolder.createFile(subFolderName + '_JSON', res1, MimeType.PLAIN_TEXT)
}
}
return { "message": "Process Complete" };
}
function getAllFileNames(fObj) {
if (fObj.scriptId) {
const base = "https://script.googleapis.com/v1/projects/"
const url1 = base + fObj.scriptId + "/content";
const options = { "method": "get", "muteHttpExceptions": true, "headers": { "Authorization": "Bearer " + ScriptApp.getOAuthToken() } };
const r = UrlFetchApp.fetch(url1, options);
const data = JSON.parse(r.getContentText());
const files = data.files;
const names = files.map(file => file.name);
return names;
}
}
html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style>
input {margin: 2px 5px 2px 0;}
#btn3,#btn4{display:none}
</style>
</head>
<body>
<form>
<input type="text" id="scr" name="script" size="60" placeholder="Enter Apps Script Id" onchange="getFileNames();" />
<br /><input type="text" id="fldr" name="folder" size="60" placeholder="Enter Backup Folder Id" />
<div id="shts"></div>
<br /><input type="button" value="0" onClick="unCheckAll();" size="15" id="btn3" />
<input type="button" value="1" onClick="checkAll();"size="15" id="btn4"/>
<br /><input type="checkbox" id="files" name="saveFiles" checked /><label for="files">Save Files</label>
<br /><input type="checkbox" id="json" name="saveJson" checked /><label for="json">Save JSON</label>
<br /><input type="button" value="Submit" onClick="backupFiles();" />
</form>
<script>
function getFileNames() {
const scriptid = document.getElementById("scr").value;
google.script.run
.withSuccessHandler((names) => {
document.getElementById('btn3').style.display = "inline";
document.getElementById('btn4').style.display = "inline";
names.forEach((name,i) => {
let br = document.createElement("br");
let cb = document.createElement("input");
cb.type = "checkbox";
cb.id = `cb${i}`;
cb.name = `cb${i}`;
cb.className = "cbx";
cb.value = `${name}`;
cb.checked = true;
let lbl = document.createElement("label");
lbl.htmlFor = `cb${i}`;
lbl.appendChild(document.createTextNode(`${name}`));
document.getElementById("shts").appendChild(cb);
document.getElementById("shts").appendChild(lbl);
document.getElementById("shts").appendChild(br);
});
})
.getAllFileNames({scriptId:scriptid});
}
function unCheckAll() {
let btns = document.getElementsByClassName("cbx");
console.log(btns.length);
for(let i =0;i<btns.length;i++) {
btns[i].checked = false;
}
}
function checkAll() {
let btns = document.getElementsByClassName("cbx");
console.log(btns.length)
for(let i = 0;i<btns.length;i++) {
btns[i].checked = true;
}
}
function backupFiles() {
console.log('backupFiles');
sObj = {};
sObj.script = document.getElementById('scr').value;
sObj.folder = document.getElementById('fldr').value;
sObj.saveJson = document.getElementById('json').checked?'on':'';
sObj.saveFiles = document.getElementById('files').checked?'on':'';
sObj.selected = [];
console.log("1");
const cbs = document.getElementsByClassName("cbx");
let selected = [];
for(let i = 0;i<cbs.length; i++) {
let cb = cbs[i];
if(cb.checked) {
sObj.selected.push(cb.value)
}
}
console.log("2");
google.script.run
.withSuccessHandler(function(obj){google.script.host.close();})
.scriptFilesBackup(sObj);
console.log(JSON.stringify(sObj));
}
</script>
</body>
</html>
There maybe some helper functions that I'm missing let me know. I use this all of the time to backup my code.
A typical back up looks like this:
Tanaike helped me with this amazing script using Cheerio. The original was for Letterboxd, but this one is to pull in a watchlist from Trakt.tv (sample watchlist).
As it is right now it pulls in the watched date and the title, but I'd also like to pull in the content from the meta tag for each item.
<meta content="8 Million Ways to Die (1986)" itemprop="name">
I tried using $('[itemprop="name"]').attr('content'); but it doesn't accept the .attr piece.
Here is the full script as it is now, returning the watched date in the Col1 and the title in Col2.
/**
* Returns Trakt watchlist by username
* #param pages enter the number of pages in the list. Default is 10
* #customfunction
*/
function TRAKT(pages=10) {
const username = `jerrylaslow`
const maxPage = pages;
const reqs = [...Array(maxPage)].map((_, i) => ({ url: `https://trakt.tv/users/`+ username +`/history/all/added?page=${i + 1}`, muteHttpExceptions: true }));
return UrlFetchApp.fetchAll(reqs).flatMap((r, i) => {
if (r.getResponseCode() != 200) {
return [["Values couldn't be retrieved.", reqs[i].url]];
}
const $ = Cheerio.load(r.getContentText());
const ar = $(`a.titles-link > h3.ellipsify, h4 > span.format-date`).toArray();
return [...Array(Math.ceil(ar.length / 2))].map((_) => {
const temp = ar.splice(0, 2);
const watchDate = Utilities.formatDate(new Date($(temp[1]).text().trim().replace(/T|Z/g, " ")),"GMT","yyyy-MM-dd");
const title = $(temp[0]).text().trim();
return [watchDate,title];
});
});
}
The values can be pulled with this, so I know there isn't any sort of blocking in play.
=IMPORTXML(
"https://trakt.tv/users/jerrylaslow/history",
"//meta[#itemprop='name']/#content")
Any help is appreciated.
In order to achieve your goal, in your script, how about the following modification?
Modified script:
function TRAKT(pages = 10) {
const username = `jerrylaslow`
const maxPage = pages;
const reqs = [...Array(maxPage)].map((_, i) => ({ url: `https://trakt.tv/users/` + username + `/history/all/added?page=${i + 1}`, muteHttpExceptions: true }));
return UrlFetchApp.fetchAll(reqs).flatMap((r, i) => {
if (r.getResponseCode() != 200) {
return [["Values couldn't be retrieved.", reqs[i].url]];
}
const $ = Cheerio.load(r.getContentText());
const ar = $(`a.titles-link > h3.ellipsify, h4 > span.format-date`).toArray();
return [...Array(Math.ceil(ar.length / 2))].map((_) => {
const temp = ar.splice(0, 2);
const c = $(temp[0]).parent('a').parent('div').parent('div').find('meta').toArray().find(ff => $(ff).attr("itemprop") == "name"); // Added
const watchDate = Utilities.formatDate(new Date($(temp[1]).text().trim().replace(/T|Z/g, " ")), "GMT", "yyyy-MM-dd");
const title = $(temp[0]).text().trim();
return [watchDate, title, c ? $(c).attr("content") : ""]; // Modified
});
});
}
When this modified script is run, the value of content is put to 3rd column. If you want to put it to other column, please modify return [watchDate, title, c ? $(c).attr("content") : ""];.
I am trying to decrypt AES with GAS. The target of decryption is a document file retrieved by Amazon Selling Partner API.
The key, iv, and URL are obtained by the API, and I want to decrypt the data downloaded by accessing the URL with the key and iv.
However, the decrypted text is either empty or garbled.
Can you please tell me what is wrong with the following code? The code uses cCryptoGS, which is a wrapper library for CryptoJS.
const decrypt_test = () => {
const url = 'https://tortuga-prod-fe.s3-us-west-2.amazonaws.com/%2FNinetyDays/amzn1.tortuga.3.5d4685fe-cdf1-4f37-8dfc-a25b85468e34.T1J5QXLEXAMPLE';
const response = UrlFetchApp.fetch(url);
const file = response.getContentText();
const key = 'xiZ8FGT6pYo49ZwfvAplJxKgO0qW46Morzs5aEXAMPLE';
const iv = 'aoGh0rhbB3ALlCFKiEXAMPLE';
const enc_key = cCryptoGS.CryptoJS.enc.Base64.parse(key);
const enc_iv = cCryptoGS.CryptoJS.enc.Base64.parse(iv);
const cipherParams = cCryptoGS.CryptoJS.lib.CipherParams.create({
ciphertext: file//cCryptoGS.CryptoJS.enc.Base64.parse(file)
});
console.log(`enc_key_length:${enc_key.words.length}`);
console.log(`enc_iv_length:${enc_iv.words.length}`);
const decryptedMessage = cCryptoGS.CryptoJS.AES.decrypt(cipherParams, enc_key, { iv: enc_iv, mode: cCryptoGS.CryptoJS.mode.CBC}).toString();
console.log(`decryptedMessage:${decryptedMessage}`);
return decryptedMessage;
};
[output]
2021/06/20 20:04:04 debug enc_key_length:8
2021/06/20 20:04:04 debug enc_iv_length:4
2021/06/20 20:04:04 debug decryptedMessage:bfc095f3ecec221e8585ceb68031078d25112f5f26ea2c1f80470f5f4f19f2e1c2cd94638e8666c3486fa29191b568bcd9e8d5a3bdcbbc05456f0567bb6cdae675fa044f94e560379d16b1d370cd7c4a9c5afbbcf4fde2694ed01c1b7950eaabc65e46c4640d8f0814bfe66e8ae65f7768136ac4615624be25373d665ee8fde82742e26664d7c09c61ac8994dc3052f0f22d5042f0b407d696e3c84a3906350dc60c46001ef7865d0c6594c57c5af22616688e028f52d4f12b538d0580c420fdcb0ee61287d4ee2629cd7d39f739d63e84dd75e948eaffb4383076f0c66997
The following code solved the problem
const decrypt_test = () => {
const url = 'https://tortuga-prod-fe.s3-us-west-2.amazonaws.com/%2FNinetyDays/EXAMPLE';
let options = {
'method': 'get',
'muteHttpExceptions': true,
};
const response = UrlFetchApp.fetch(url, options);
const file = response.getBlob().getBytes();
const key = 'xiZ8FGT6pYo49ZwfvAplJxKgO0qW46MoEXAMPLE';
const iv = 'aoGh0rhbB3ALlCFKiuJEXAMPLE';
const enc_key = cCryptoGS.CryptoJS.enc.Base64.parse(key);
const enc_iv = cCryptoGS.CryptoJS.enc.Base64.parse(iv);
const cipherParams = cCryptoGS.CryptoJS.lib.CipherParams.create({
ciphertext: cCryptoGS.CryptoJS.enc.Hex.parse(hexes(file))
});
const decryptedMessage = cCryptoGS.CryptoJS.AES.decrypt(cipherParams, enc_key,
{ iv: enc_iv, mode: cCryptoGS.CryptoJS.mode.CBC}).toString();
console.log(`decryptedMessage:${decryptedMessage}`);
const bin = bytes(decryptedMessage)
const myBlob = Utilities.newBlob(bin, MimeType.TEXT, "decrypted.csv");
DriveApp.createFile(myBlob);
};
const bytes = (hexstr) => {
ary = [];
for (var i = 0; i < hexstr.length; i += 2) {
ary.push(parseInt(hexstr.substr(i, 2), 16));
}
return ary;
}
const hexes = (ary) => {
return ary.map((e) => ( '00' + (e < 0 ? e += 0x0100 : e).toString(16)).slice(-2)).join('')
}
I cannot figure out why this is not working, so please help me figure it out.
Display.js :
render(){
const notesList = this.props.notesList;
const displayNotes = notesList.map( (note,note_id) =>
<div className="display">
<p id={'note-' + note_id}>{note}</p>
<button type="button" className="edit-button" onClick={()=>this.props.edit(note_id)}>Edit</button>
<button type="button" className="delete-button" onClick={()=>this.props.delete(note_id)}>Delete</button>
</div> );
return <div>{displayNotes}</div>;
}
}
App.js:
handleEdit = (note_id) => {
const id = 'note-' + note_id;
document.getElementById(id).contentEditable = 'true';
}
handleDelete = (note_id) => {
const id = 'note-' + note_id;
document.getElementById(id).display = 'none';
}
When you want to change an element css property, you need to access the style object first.
This is how you would get it:
handleDelete = (note_id) => {
const id = 'note-' + note_id;
document.getElementById(id).style.display = 'none';
}