I have a quick question regarding google sheets api using java script.
Can I enter multiple ranges (e.g. named ranges) at once to keep the number of requests low?
The code I have so far:
const RANGE = "'GM Cheat Sheet'!";
const response = await fetch(
`https://sheets.googleapis.com/v4/spreadsheets/${ID}/values/${RANGE}?key=${API_KEY}`
);
const { values } = await response.json();
console.log(values);
Any help is appreciated :)
I tried defining the range as follows:
const RANGE = ["'GM Cheat Sheet'!Range1", "'GM Cheat Sheet'!Range2"];
This did not work.
In your situation, how about using "Method: spreadsheets.values.batchGet"? When this method is used, the multiple ranges can be used by one API call. When this is reflected in your script, it becomes as follows.
Modified script:
const API_KEY = "###"; // Please set your API key.
const ID = "###"; // Please set your Spreadsheet ID.
const RANGE = ["Range1", "Range2"]; // This is from your question.
const ranges = RANGE.map(e => `ranges=${encodeURIComponent(e)}`).join("&");
const response = await fetch(`https://sheets.googleapis.com/v4/spreadsheets/${ID}/values:batchGet?key=${API_KEY}&${ranges}`);
const { valueRanges } = await response.json();
const obj = valueRanges.reduce((o, { values }, i) => (o[RANGE[i]] = values, o), {});
console.log(obj);
When this script is run, the values are retrieved from the ranges of RANGE.
In the case of a named range, the name of the named range is a unique value in the Google Spreadsheet. So, in this case, when "Range1" and "Range2" are the named range, you can use const RANGE = ["Range1", "Range2"];.
By adding const res = valueRanges.reduce((o, { values }, i) => (o[RANGE[i]] = values, o), {});, the returned value is as follows. By this, you can retrieve the values of the named range as obj.Range1 and obj["Range1"].
{
"Range1":[### values ###],
"Range2":[### values ###]
}
Reference:
Method: spreadsheets.values.batchGet
Related
I want to copy values from one range to another sheet (in the same workbook) using Excel JavaScript, that is, taking some range values and pass them to another sheet.
The Excel Javascript API shows how to copy one range to another range in the same worksheet:
let sheet = context.workbook.worksheets.getItem("Sample");
// Copy everything from "A1:E1" into "G1" and the cells afterwards ("G1:K1").
sheet.getRange("G1").copyFrom("A1:E1");
await context.sync();
});
However I can't achieve to do it in another worksheet (of the same workbook).
This is the code that I'm using but it shows me some errors about using wrong parameters in the methods.
await Excel.run(async (context) => {
const captura = context.workbook.worksheets.getItem("Captura");
const historico = context.workbook.worksheets.getItem("Historico");
var rango = captura.getRange("A3:F5");
var i = historico.getUsedRange().getLastRow();
historico.getRange("A" + i).copyFrom(rango);
await context.sync();
});
}
I have written a code to populate data from a spreadsheet into a google doc and save it to drive using g-sript. Here is the code for the same :
function onOpen() {
const ui = SpreadsheetApp.getUi();
const menu = ui.createMenu('Invoice creator');
menu.addItem('Generate Invoice', 'invoiceGeneratorFunction');
menu.addToUi();
}
function invoiceGeneratorFunction() {
const invoiceTemplate = DriveApp.getFileById('125NPu-n77F6N8hez9w63oSzbWrtryYpRGOkKL3IbxZ8');
const destinationFolder = DriveApp.getFolderById('163_wLsNGkX4XDUiSOcQ88YOPe3vEx7ML');
const sheet_invoice = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('New Invoice Sheet');
const rows = sheet_invoice.getDataRange().getValues();
Logger.log(rows);
rows.forEach(function(row, index) {
if (index === 0) return;
if (row[12] != "") return;
const copy = invoiceTemplate.makeCopy(`${row[1]} VIN Number: ${row[2]}`,destinationFolder);
const doc = DocumentApp.openById(copy.getId());
const body = doc.getBody();
var friendlyDateBilled = new Date(row[0]).toLocaleDateString();
var friendlyDateDelivery = new Date(row[3]).toLocaleDateString();
body.replaceText('{{Date Billed}}',friendlyDateBilled);
body.replaceText('{{Customer Name}}',row[1]);
body.replaceText('{{VIN Number}}',row[2]);
body.replaceText('{{Date of Delivery}}',friendlyDateDelivery);
body.replaceText('{{Package}}',rows[4]);
body.replaceText('{{Price}}',rows[5]);
body.replaceText('{{Output CGST}}',rows[6]);
body.replaceText('{{Output SGST}}',rows[7]);
body.replaceText('{{Discount}}',rows[8]);
body.replaceText('{{Total Price}}',rows[9]);
body.replaceText('{{Balance}}',rows[10]);
body.replaceText('{{Remarks}}',rows[11]);
doc.saveAndClose();
const url = doc.getUrl();
sheet_invoice.getRange(index+1, 13).setValue(url);
})
}
I have created a menu button for the script to run. But when i run it I get an error saying :
Exception: Invalid argument: replacement
at unknown function
at invoiceGeneratorFunction(Code:17:8)
(Here line 32 is body.replaceText('{{Package}}',rows[4]);
and line 17 is the start of forEach)
Interestingly when I comment out the rest of body.replaceText lines after that line, the code works. I can't understand what the problem is, if it's working if I comment out the lines.
In your script, rows is 2 dimensional array retrieved with sheet_invoice.getDataRange().getValues(). When I saw your loop, after the line of body.replaceText('{{Package}}',rows[4]);, rows is used. In this case, rows[4] is 1-dimensional array. It is required to be the string for the arguments of replaceText(searchPattern, replacement). I think that this might be the reason for your issue. In order to remove this issue, how about the following modification?
From:
body.replaceText('{{Package}}',rows[4]);
body.replaceText('{{Price}}',rows[5]);
body.replaceText('{{Output CGST}}',rows[6]);
body.replaceText('{{Output SGST}}',rows[7]);
body.replaceText('{{Discount}}',rows[8]);
body.replaceText('{{Total Price}}',rows[9]);
body.replaceText('{{Balance}}',rows[10]);
body.replaceText('{{Remarks}}',rows[11]);
To:
body.replaceText('{{Package}}',row[4]);
body.replaceText('{{Price}}',row[5]);
body.replaceText('{{Output CGST}}',row[6]);
body.replaceText('{{Output SGST}}',row[7]);
body.replaceText('{{Discount}}',row[8]);
body.replaceText('{{Total Price}}',row[9]);
body.replaceText('{{Balance}}',row[10]);
body.replaceText('{{Remarks}}',row[11]);
Note:
I'm not sure about your actual values of rows. So I'm not sure whether the values of row[4] to row[11] are what you want. If those values are not the values you expect, please check your Spreadsheet again.
Reference:
replaceText(searchPattern, replacement)
Building a script in google apps script.
I get values from an invoice data sheet with multiple lines per invoice so as to account for line items.
My progress so far has been to extract individual invoice numbers from the column (each invoice number occurs as many line items the individual invoice has).
The array todaysInvoices looks like this: [35033817, 35033818, 35033819, 35033820, 35033821]
Now, I need a way to create an object for each of these invoice numbers that has different properties (such as invoiceDate and customerName etc.). The initial invoice number as in the array should thereby be assigned as 'id' property to the new invoice object.
I need help to use objects in javascript.
If you require additional information, please let me know.
Below is a screenshot of a simplified version of my order sheet:
This is a clipping of my order sheet. Before and after the shown columns there are many more with more details but the hierarchies of information are already in the image
Below is the code I have so far:
const orderSheet = SpreadsheetApp.openById('SPREADSHEETID').getSheetByName('SHEETNAME');
const invoiceTemplate = DriveApp.getFileById('DOCUMENTID');
const tempFolder = DriveApp.getFolderById('FOLDERID');
const invoiceData = orderSheet.getRange(4,7, orderSheet.getLastRow() - 1, 57).getDisplayValues().filter(function (rows){ return rows[0] === 'INVOICED'});
const invDataRepo = SpreadsheetApp.openById('SPREADSHEETID2');
var timestamp = new Date();
function printBulkInvoices() {
logLineItems ();
var todaysInvoices = uniqueInvIDs ();
todaysInvoices.sort();
todaysInvoices.map(String);
//fetchInvData (todaysInvoices);
Logger.log (todaysInvoices)
}
function fetchInvData (invoiceIDs) {
let invoices = {
}
Logger.log(invoices)
invoiceIDs.forEach
}
function fetchLineItems (invoiceDataArray) {
}
// send array of todays unique invoice numbers (later all inv data?) to invdata sheet and log them
function logTodaysInvoices (invIDArr){
invIDArr.forEach
invDataRepo.getSheetByName('invdata').getRange(invDataRepo.getSheetByName('invdata').getLastRow()+1,1,invIDArr.length,1).setValue(invIDArr);
}
// return an array of unique invoice ids from todays invoice data
function uniqueInvIDs (){
let singleArray = invoiceData.map(row => row[5]);
let unique = [...new Set(singleArray)];
return unique;
}
//log incoicedata to invdatarepo-sheet 'lineitems'
function logLineItems (){
invDataRepo.getSheetByName('lineitems').getRange(invDataRepo.getSheetByName('lineitems').getLastRow()+1,2,invoiceData.length,invoiceData[0].length).setValues(invoiceData);
}
It's hard to say exactly what you need since we cannot see your Invoice Data Sheet.
But here's something that might give you a start:
let iobj = {idA:[]};
[35033817, 35033818, 35033819, 35033820, 35033821].forEach((id => {
if(!iobj.hasOwnProperty(id)) {
iobj[id]={date: invoiceDate, name: customName, items:[]};
iobj.idA.push(id);//I find it handy to have an array of object properties to loop through when I wish to reorganize the data after it's all collected
} else {
iobj[id].items.push({item info properties});//I am guessing here that you may wish to addition additional information about the items which are on the current invoice
}
});
Javascript Object
To follow up from your question:
Your loop to collect object data would start to look something like this:
function getInvoiceData() {
const ss = SpreadsheetApp.getActive();
const ish = ss.getSheetByName('Invoice Data');
const isr = 2;
const hA = ish.getRange(1, 1, 1, ish.getLastColumn()).getValues()[0];
let idx = {};//object return head index into row array based on header title which in this case I assume invoice number is labeled 'Invoicenumber'
hA.forEach((h, i) => {idx[h] = i});
const vs = ish.getRange(isr, 1, ish.getLastRow() - isr + 1, ish.getLastColumn()).getValues();
let iobj = { idA: [] };
vs.forEach(r => {
if (!iobj.hasOwnProperty(r[idx['invoicenumber']])) {
iobj[r[idx['invoicenumber']]] = { date: r[idx['invoicedate']], name: r[idx['customername']], items: [] };
iobj.idA.push(r[idx['invoicenumber']]);
} else {
iobj[r[idx['invoicenumber']]].items.push({ iteminfoproperties:'' });
}
});
}
Currently I figured out how to call the YGOPRO API in a React function and paste the data inside a stateless component. But now I've come at an impasse when trying to figure out how to use some of the API data to run some float numbers through a process of generating new values to post instead.
Here's the full scenario. I originally made this exact thing in a basic HTML layout because that's what the official YGO forbidden/limited webpage used and what I edited to make a joke video about the f/l list months ago. it still works fairly well and the API displays just fine, but its the matter of using this forloop and math junction code to work in a React environment
This is the getJSON part of the code from the initial HTML project:
$.getJSON(
endpoint + "?startprice=" + startprice + "&endprice=" + endprice,
function (json) {
$.each(json.data, function (ix, obj) {
let cards = [];
let name = obj.name;
let type = obj.type;
let card_sets = obj.card_sets;
let price_array = [];
if (card_sets === undefined) {
return true;
}
for (let i = 0; i < card_sets.length; i++) {
let set_price = parseFloat(card_sets[i].set_price);
if (set_price === 0 || set_price === null || set_price === "0.00") {
continue;
} else {
price_array.push(set_price);
}
}
let min_price = Math.min(...price_array);
let max_price = Math.max(...price_array);
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2,
});
let min_price_usd = formatter.format(min_price);
let max_price_usd = formatter.format(max_price);
console.log(name);
console.log(min_price);
What this originally did was simply read each monster entry in the API call within the price values described and grabbed each price value from each set listed within each entry while returning true to any part of the entry that didn't have a valid price value. it would then put all the valid money values in a temp array for that monster entry, use the Math function to get both a minimum and maximum price value from that API and then format them to appear as USD before placing them in the front-end of their respective monster entry.
This is the section of the React function that calls and collects the data for the API, including some throwaway lines that I left in after trying to figure all this out:
export default function TestView() {
// for the Banned cards, or all cards more than $5. The limited section for cards more than $1 will come later
// const name = "Dark Magician";
const startprice = 5.0;
const endprice = 99999.99;
const [data, setData] = useState([]);
// how to apply api names to these values?
let name = data.name;
let type = data.type;
console.log(name);
let price_array = [];
useEffect(() => {
getCardDataByPrice(startprice, endprice)
.then(({ data }) => {
setData(data.data);
})
.catch((error) => console.error(`Error: ${error}`));
}, []);
function getCardDataByPrice(startprice, endprice) {
const ygoproURL = "https://db.ygoprodeck.com/api/v7/cardinfo.php";
let ygoproEndpoint = `${ygoproURL}?startprice=${startprice}&endprice=${endprice}`;
if (startprice) {
ygoproEndpoint += `&startprice=${startprice}`;
}
if (endprice) {
ygoproEndpoint += `&endprice=${endprice}`;
}
return axios.get(ygoproEndpoint);
}
// most of the code used to convert money values of each api entry
// This is where the function that grabs the API values for card set prices was to be gathered and calculated like in the original HTML.
let min_price = Math.min(...price_array);
let max_price = Math.max(...price_array);
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2,
});
let min_price_usd = formatter.format(min_price);
let max_price_usd = formatter.format(max_price);
console.log(name);
console.log(min_price);
return (...
...etc.
What I am trying to figure out is how I could grab that API data for each monster entry and start grabbing the cash value from each set like how I used to do it here. I'm assuming its within the function that would call the Endpoint in the first place, but I'm not sure.
tl;dr: trying to port API call code from an HTML site into React is being hampered by not knowing where I can grab and alter the data to stick it to the front-end.
You can find the original HTML source code here to help you understand what I'm trying to do.
https://github.com/patrickfinnigan/yugioh_banlist_manipulation
And you can also find how I'm trying to do the same thing in React, in case you'd need to see more of the overall code.
https://github.com/patrickfinnigan/custom_yugioh_banlists_react
(The Relative path would be 'ygo_banlists\src\views\TestView.js')
How can I insert values into a blank sheet?
The way that I currently know of for inserting values is by retrieving the range and then inserting the values using the values property, such as
range.getRange("A1:" + cellBottomRight);
range.load("values");
context.sync().then(function () {
range.values = twoDimensionalArrayOfValues;
});
Is there a simpler way by using a single function to simply insert the values, rather than first retrieving the range?
Thanks!
EDIT:
I've been trying to create a new sheet and then insert a 2-dimensional array to it, which its values would be inserted starting from the cell A1.
So far, I managed to do the following:
let neeSheet = context.workbook.worksheets.add("New sheet");
newSheet.activate();
newSheet.getRange("A1").values = twoDimensionalArray;
context.sync();
But didn't work.
How can I get it to work?
Thanks!
You don't actually have to load the range if you just want to set the values, so you can do this:
var range = ctx.workbook.worksheets.getItem("Sheet1").getRange("A1");
range.values = [["Value"]];
return ctx.sync();
(Adding this answer in response to the new information that #avi12 added to the question above, under "EDIT")
Philip's answer above correctly shows how to insert a single value into cell A1 of a worksheet. To address the specific scenario that you've described in your (updated) question, here are some code snippets (one in TypeScript and the other in JavaScript) that show how to create a new worksheet and then add data to the worksheet, using a 2-dimensional array of data. The key thing to point out here is that I'm retrieving the range by using getResizedRange (passing in dimensions of my array), so that the size of the range matches the size of the data set that I'm inserting into it.
Note: You can quickly and easily try these snippets yourself by using Script Lab (https://aka.ms/getscriptlab). Simply install the Script Lab add-in (free), then choose "Import" in the navigation menu, and use the following GIST URL: https://gist.github.com/kbrandl/01c4faf352c34286188311c1198f6307.
TypeScript:
async function run_TS() {
try {
await Excel.run(async (context) => {
// define values that will be inserted into new sheet
let values = [["A1", "B1", "C1"], ["A2", "B2", "C2"]];
// create and activate new sheet
let sheets = context.workbook.worksheets;
let newSheet = sheets.add("New sheet TS");
newSheet.activate();
// add data to the new sheet
let range = newSheet.getRange("A1").getResizedRange(values.length - 1, values[0].length - 1);
range.values = values;
// sync
await context.sync();
console.log("Finished with run_TS function");
});
}
catch (error) {
OfficeHelpers.UI.notify(error);
OfficeHelpers.Utilities.log(error);
}
}
JavaScript:
function run_JS() {
Excel.run(function (context) {
// define values that will be inserted into new sheet
var values = [["A1", "B1", "C1"], ["A2", "B2", "C2"]];
// create and activate new sheet
var sheets = context.workbook.worksheets;
var newSheet = sheets.add("New sheet JS");
newSheet.activate();
// add data to the new sheet
var range = newSheet.getRange("A1").getResizedRange(values.length - 1, values[0].length - 1);
range.values = values;
// sync
return context.sync()
.then(function () {
console.log("Finished with run_JS function");
});
})
.catch(function (error) {
OfficeHelpers.UI.notify(error);
OfficeHelpers.Utilities.log(error);
});
}