How do you loop through an object and replace text - javascript

I have a script that should create a pdf file from a google form submission and grabs the data to be changed as an object. However I am using the replaceText action to make the changes to the doc and I'm getting the following error.
Exception: Invalid argument: replacement
at Create_PDF(Code:37:8)
at After_Submit(Code:13:19)
It is supposed to change the values in the generated doc file and it worked when I used the namedValues function. However now that I'm using range instead it doesn't seem to work.
function After_Submit(e){
var range = e.range;
var row = range.getRow(); //get the row of newly added form data
var sheet = range.getSheet(); //get the Sheet
var headers = sheet.getRange(1, 1, 1, 129).getValues().flat(); //get the header names from A-O
var data = sheet.getRange(row, 1, 1, headers.length).getValues(); //get the values of newly added form data + formulated values
var values = {}; // create an object
for( var i = 0; i < headers.length; i++ ){
values[headers[i]] = data[0][i]; //add elements to values object and use headers as key
}
Logger.log(values);
const pdfFile = Create_PDF(values);
sendEmail(e.namedValues['Email Address to Receive File '][0],pdfFile);
}
function sendEmail(email,pdfFile){
GmailApp.sendEmail(email, "Subject", "Files Attached", {
attachments: [pdfFile],
name: "From Email"
});
}
function Create_PDF(values) {
const PDF_folder = DriveApp.getFolderById("ID_1");
const TEMP_FOLDER = DriveApp.getFolderById("ID_2");
const PDF_Template = DriveApp.getFileById('ID_3');
const newTempFile = PDF_Template.makeCopy(TEMP_FOLDER);
const OpenDoc = DocumentApp.openById(newTempFile.getId());
const body = OpenDoc.getBody();
console.log(body);
body.replaceText("{{Timestamp}}", values['Timestamp'][0]);
body.replaceText("{{Location}}", values['Location'][0]);
body.replaceText("{{Item1}}", values['Item1'][0]);
body.replaceText("{{Item2}}", values['Item2'][0]);
body.replaceText("{{Itme3}}", values['Item3'][0]);
body.replaceText("{{e1}}", values['e1'][0]);
body.replaceText("{{e2}}", values['e2'][0]);
body.replaceText("{{e3}}", values['e3'][0]);
body.replaceText("{{e4}}", values['e4'][0]);
body.replaceText("{{e5}}", values['e5'][0]);
body.replaceText("{{e6}}", values['e6'][0]);
body.replaceText("{{e7}}", values['e7'][0]);
body.replaceText("{{e8}}", values['e8'][0]);
body.replaceText("{{e9}}", values['e9'][0]);
body.replaceText("{{e10}}", values['e10'][0]);
body.replaceText("{{e11}}", values['e11'][0]);
body.replaceText("{{e12}}", values['e12'][0]);
body.replaceText("{{e13}}", values['e13'][0]);
body.replaceText("{{e14}}", values['e14'][0]);
body.replaceText("{{e15}}", values['e15'][0]);
body.replaceText("{{e16}}", values['e16'][0]);
body.replaceText("{{e17}}", values['e17'][0]);
body.replaceText("{{e18}}", values['e18'][0]);
body.replaceText("{{e19}}", values['e19'][0]);
body.replaceText("{{e20}}", values['e20'][0]);
body.replaceText("{{e21}}", values['e21'][0]);
body.replaceText("{{e22}}", values['e22'][0]);
body.replaceText("{{e23}}", values['e23'][0]);
body.replaceText("{{e24}}", values['e24'][0]);
body.replaceText("{{e25}}", values['e25'][0]);
body.replaceText("{{e26}}", values['e26'][0]);
body.replaceText("{{e27}}", values['e27'][0]);
body.replaceText("{{e28}}", values['e28'][0]);
body.replaceText("{{e29}}", values['e29'][0]);
body.replaceText("{{e30}}", values['e30'][0]);
body.replaceText("{{e31}}", values['e31'][0]);
body.replaceText("{{e32}}", values['e32'][0]);
body.replaceText("{{e33}}", values['e33'][0]);
body.replaceText("{{e34}}", values['e34'][0]);
body.replaceText("{{e35}}", values['e35'][0]);
body.replaceText("{{e36}}", values['e36'][0]);
body.replaceText("{{e37}}", values['e37'][0]);
body.replaceText("{{e38}}", values['e38'][0]);
body.replaceText("{{e39}}", values['e39'][0]);
body.replaceText("{{H1}}", values['H1'][0]);
body.replaceText("{{H2}}", values['H2'][0]);
body.replaceText("{{H3}}", values['H3'][0]);
body.replaceText("{{H4}}", values['H4'][0]);
body.replaceText("{{H5}}", values['H5'][0]);
body.replaceText("{{H6}}", values['H6'][0]);
body.replaceText("{{H7}}", values['H7'][0]);
body.replaceText("{{H8}}", values['H8'][0]);
body.replaceText("{{H9}}", values['H9'][0]);
body.replaceText("{{H10}}", values['H10'][0]);
body.replaceText("{{H11}}", values['H11'][0]);
body.replaceText("{{H12}}", values['H12'][0]);
body.replaceText("{{H13}}", values['H13'][0]);
body.replaceText("{{H14}}", values['H14'][0]);
OpenDoc.saveAndClose();
const BLOBPDF = newTempFile.getAs(MimeType.PDF);
const pdfFile = PDF_folder.createFile(BLOBPDF).setName("FLHA");
console.log("The file has been created ");
return pdfFile;
}

Your question was how to loop through an object and replace text
This creates an object from Sheet0:
Sheet0:
one
pattern
two
this is the pattern
three
pattern pattern
four
nothing
five
nothing
Code:
function replacepattern() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const vs = sh.getRange(1,1,sh.getLastRow(), 2).getValues();
//creating object from spreadsheet
let obj = {pA:[]};
vs.forEach(r =>{
obj[r[0]]=r[1];
obj.pA.push(r[0]);
});
Logger.log(JSON.stringify(obj));
let oA = obj.pA.map(p => [obj[p].replace(/pattern/g,'replacement')]);//doing the replacement in an object
sh.getRange(1,sh.getLastColumn() + 1,oA.length, oA[0].length).setValues(oA);//outputting the replaced string in the next column
Logger.log(JSON.stringify(oA));
}
Sheet0 after running once:
one
pattern
replacement
two
this is the pattern
this is the replacement
three
pattern pattern
replacement replacement
four
nothing
nothing
five
nothing
nothing

This is related to my answer here.
The error code Exception: Invalid argument: replacement at Create_PDF(Code:37:8) at After_Submit(Code:13:19) is caused by the null value of values['Timestamp'][0]. If you try to print the data type of values['Timestamp'], it will return a type object, since that object does not have value for index 0 to it will return a null value.
For entries that are type String, if you add [0] to it, it will return only the first element of the string. Example you have "Test" string, adding [0] to it will return "T"
To fix that, just remove the [0] in all of body.replaceText(..., values['...'][0]) entries.
OR
Loop through values object by replacing the body.replaceText entries in your code with this:
for (const key in values) {
body.replaceText("{{"+key+"}}", values[key]);
}
Example usage:
Form inputs:
Output:
Reference:
JavaScript for..in

Related

(JS) Google Script App Search / Filter for keyword

I have been stumped on this for a while. I am fairly new to Google script app and wanted to see if there is a way to make this happen. So far, I've used a few methods within Google Sheet but seem to not get it working.
The code below does give me an output of all the data, however, the data that is nested in the data.custom_fields[x] has multiple objects that is separated by ",". I would like to be able to filter out the other key words and just use whatever is inside "display_value=". The display_value= is not always in the same area so have to run a search for them.
I am assuming some kind of If statement would be used here..
An example of the object is:
{type=x, resource_subtype=x, created_by={name=x, gid=x, resource_type=x}, display_value=Cool Value, description=x, enabled=x, resource_type=custom_field, gid=x, enum_options=[x.lang.Object;x, enum_value={x}, name=x}
I've tried to split function as well but not sure how to filter out the words I need.
function Users() {
var options = {
"headers" : {
"Authorization": "API Key here"
}
}
var response = UrlFetchApp.fetch("URL here", options);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var sheet = ss.getSheetByName("Tab Name here"); // specific sheet name getSheetByName(""); alternatively use ss.getActiveSheet()
var dataAll = JSON.parse(response.getContentText()); //
var dataSet = dataAll.data; // "data" is the key containing the relevant objects
var rows = [],
data;
for (i = 0; i < dataSet.length; i++) {
data = dataSet[i];
rows.push([
data.gid,
data.name,
data.permalink_url,
data.due_on,
data.custom_fields[1],
data.custom_fields[2],
data.custom_fields[4],
data.custom_fields[5],
data.custom_fields[6],
data.custom_fields[7],
data.custom_fields[8],
data.custom_fields[9],
]); //your JSON entities here
}
// [row to start on], [column to start on], [number of rows], [number of entities]
dataRange = sheet.getRange(2, 1, rows.length, 12);
dataRange.setValues(rows);
Thank you in advance!
Example Image of JSON imported data
Although they appear separated by ,'s, that is only how they're displayed in the log. Because you're using JSON.parse, you're receiving/converting to an Object, not a string.
Because data.custom_fields is an array of objects, you can access the property/key values as : data.custom_fields[x].display_value.
Learn More:
JSON.parse()
Accessing Object Properties
If you want to extract display_value, try
let myVal = myData.match(/(?<=display_value=)[^,]+/g)[0]
I guess that myData could be data.custom_fields[5], so replace it by
data.custom_fields[5].match(/(?<=display_value=)[^,]+/g)[0]

Invalid argument: replacement error when populating google doc using google script

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)

Use array of invoice numbers to create invoice objects within which each invoice number from the initial array serves as id property

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:'' });
}
});
}

Custom function in google sheet using indexOf

I've got a list of emails that I need to clean and identify which of these emails are company emails (i.e info#, hello#, etc)
I've had an idea to add rows in one google sheet, then check this against another google sheet with a column of company alias'. This will then return Trueof False in the column Company Alias? in the Input sheet.
Here is my Google sheet example.
I think it needs to iterate through the values of the second sheet and compare for email 1 field in the first sheet to see if it contains that value. I have made an apps script below:
function CHECKALIAS(x) {
var app = SpreadsheetApp;
var aliasSheet = app.getActive().getSheetByName('Company_Alias').getRange(2, 1, 45, 1);
var aliasRange = aliasSheet.getValues();
var str = x;
if(str.indexOf(aliasRange) !== -1){
return false;
} else {
return true;
}
}
I get the below error:
TypeError: Cannot read property 'indexOf' of undefined (line 13, file "Code")
Are you running the function without passing it a value? It's giving you the error because str has no value.
Anyway, there's no need for a custom function here. This will return true if there's a match with one of the aliases.
=REGEXMATCH(A1, JOIN("|",FILTER(Company_Alias!A2:A, NOT(ISBLANK(Company_Alias!A2:A)))))
str is a string and aliasRange is an array. Instead of searching for the array in the string, you should search for the string in the array.
Another problem is that .getValues​​() returns an array of arrays:
[["info #"], ["support #"], ["sales #"], ...].
To transform it into a 1D array, use the flat() function:
function CHECKALIAS(x) {
let app = SpreadsheetApp;
let aliasSheet = app.getActive().getSheetByName('Company_Alias').getRange(2, 1, 45, 1);
let aliasRange = aliasSheet.getValues().flat(); // Transforms 2D array in 1D
let userDomain = x.split('#'); // split "admin#somedomain.com" into [ "admin", "somedomain.com" ]
let usr = userDomain[0] + '#'; // "admin#
return aliasRange.indexOf(usr) !== -1; // Is usr in aliasRange?
}

Add JSON values from two different files into a single file

I just need to merge two files with the same list, but with different values on each file. Preferably in JavaScript
For example:
File 1
{"list1":{"a":1,"b":2}
{"list2":{"c":3,"d":4}
File 2
{"list1":{"a":5,"b":6}
{"list2":{"c":7,"d":8}
The desired result is
{"list1":{"a":6,"b":8}
{"list2":{"c":10,"d":12}
Sorry for the noob question, but the person who sent me the files should have done this themselves, but are currently unavailable. The files are too big to do by hand.
This is not very flexible code, but it would be far more work, to make something more dynamic. You would have to parse the objects recursevely and check if the property is an object and then jump deeper. Until ou find the values.
And please be aware that I'm not making any type checking whatsoever. If the data contains faulty data it is not cought properly. Also this code requires this exact structure. If your object contains other properties it might crash too.
// your data
const f1l1 = '{"list1":{"a":1,"b":2}}';
const f1l2 = '{"list2":{"c":3,"d":4}}';
const f2l1 = '{"list1":{"a":5,"b":6}}';
const f2l2 = '{"list2":{"c":7,"d":8}}';
var result1= JSON.parse(f1l1);
var result2= JSON.parse(f1l2);
//the names of the list as they appear in your real data *must* be the first object
const nameList1 = Object.keys(result1)[0];
const nameList2 = Object.keys(result2)[0];
//remove the list name
result1=result1[nameList1];
result2= result2[nameList2];
//get data from other file nd remove list name
const file2List1= JSON.parse(f2l1)[nameList1];
const file2List2= JSON.parse(f2l2)[nameList2];
// go through all items and sum them if the value is already in the list, else put it in for list1
for (var prop in file2List1) {
if (Object.prototype.hasOwnProperty.call(file2List1, prop)) {
if(Object.prototype.hasOwnProperty.call(result1, prop)){
result1[prop] = result1[prop] + file2List1[prop];
}else{
result1[prop] = file2List1[prop];
}
}
}
// and now for list2
for (var prop in file2List2) {
if (Object.prototype.hasOwnProperty.call(file2List2, prop)) {
if(Object.prototype.hasOwnProperty.call(result2, prop)){
result2[prop] = result2[prop] + file2List2[prop];
}else{
result2[prop] = file2List2[prop];
}
}
}
//put names of lists back in.
result1 = {[nameList1]:result1};
result2 = {[nameList2]:result2};
//check results:
console.log("input data:");
console.log(JSON.parse(f1l1));
console.log(JSON.parse(f1l2));
console.log(JSON.parse(f2l1));
console.log(JSON.parse(f2l2));
console.log("output data:");
console.log(result1);
console.log(result2);
You can try this out
newList = list1.concat(list2);

Categories