I am using getRowsData to retrieve the information, but I would like to modify the values stored within a JS object and then write that modified value back into the cell. I would like to use the normalized header variable that was created with getRowsData to write back into the cell. So if I retrive the info using
var thirdEmployee = employeeObjects[2];
var stringToDisplay = "The third column is: " + thirdEmployee.firstName + " " + thirdEmployee.lastName;
stringToDisplay += " (id #" + thirdEmployee.employeeId + ") working in the ";
stringToDisplay += thirdEmployee.department + " department and with phone number ";
stringToDisplay += thirdEmployee.phoneNumber;
ss.msgBox(stringToDisplay);
Then, for example, I would assign a new value to the Javascript object thirdEmployee.phoneNumber; and then have it written into the proper location in the range (i.e., active row, column # based on header).
thirdEmployee.phoneNumber = "123-555-5555";
thirdEmployee.phoneNumber.setNewValue();
or
setNewValue.thirdEmployee.phoneNumber;
Basically, it would be a modified version of setRowsData but allow for modification of one object and then telling that object to be written into the spreadsheet based on the active row & column based on the header.
Anyone use or know how to do this?
The setRowsData() helper function from the Writing Data from JavaScript Objects to a Spreadsheet tutorial already supports the ability to write a single object. To use it, though, you need to provide both of the optional parameters optHeadersRange and optFirstDataRowIndex.
If we assume that your headers are in row 1, here's how you could update thirdEmployee:
var headersRange = sheet.getRange(1, 1, 1, sheet.getLastColumn());
var thirdEmployee = employeeObjects[2];
...
thirdEmployee.phoneNumber = "123-555-5555";
setRowsData(sheet, [thirdEmployee], headersRange, 3 );
Related
I tried applying this solution to my case:
Emailing SPARKLINE charts sends blank cells instead of data
But when I try to apply it to my situation an error pops up with:
TypeError: Cannot read property '0' of null
On the executions there is more information about this error:
My GAS code for my Email solution is able to send just the values, and it's here:
function alertDailyInfo() {
let emailAddress = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SANDBOX").getRange("F1").getValue();
let treeIconUrl = "https://d1nhio0ox7pgb.cloudfront.net/_img/g_collection_png/standard/256x256/tree.png";
let treeIconBlob = UrlFetchApp
.fetch(treeIconUrl)
.getBlob()
.setName("treeIconBlob");
let treeUpdate = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SANDBOX").getRange("F6").getValue();
let waterUpdate = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SANDBOX").getRange("F11").getValue();
if (treeUpdate > 0) {
MailApp.sendEmail({
to: emailAddress,
subject: "TREE WATER UPDATE",
htmlBody: "<img src='cid:treeIcon'><br>" + '<br>' + '<br>' +
'<b><u>Tree average is:</u></b>'+ '<br>' + treeUpdate + '<br>' + '<br>' +
'<b><u>Water average is:</u></b>'+ '<br>' + waterUpdate + '<br>' + '<br>'
,
inlineImages:
{
treeIcon: treeIconBlob,
}
});
}
}
The code from the solution presented on the link above and which I have tried to adapt to my situation (please check my file below) is here:
drawTable();
function drawTable() {
let emailAddress1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SANDBOX").getRange("F1").getValue();
var ss_data = getData();
var data = ss_data[0];
var background = ss_data[1];
var fontColor = ss_data[2];
var fontStyles = ss_data[3];
var fontWeight = ss_data[4];
var fontSize = ss_data[5];
var html = "<table border='1'>";
var images = {}; // Added
for (var i = 0; i < data.length; i++) {
html += "<tr>"
for (var j = 0; j < data[i].length; j++) {
if (typeof data[i][j] == "object") { // Added
html += "<td style='height:20px;background:" + background[i][j] + ";color:" + fontColor[i][j] + ";font-style:" + fontStyles[i][j] + ";font-weight:" + fontWeight[i][j] + ";font-size:" + (fontSize[i][j] + 6) + "px;'><img src='cid:img" + i + "'></td>"; // Added
images["img" + i] = data[i][j]; // Added
} else {
html += "<td style='height:20px;background:" + background[i][j] + ";color:" + fontColor[i][j] + ";font-style:" + fontStyles[i][j] + ";font-weight:" + fontWeight[i][j] + ";font-size:" + (fontSize[i][j] + 6) + "px;'>" + data[i][j] + "</td>";
}
}
html += "</tr>";
}
html + "</table>"
MailApp.sendEmail({
to: emailAddress1,
subject: "Spreadsheet Data",
htmlBody: html,
inlineImages: images // Added
})
}
function getData(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SANDBOX");
var ss = sheet.getDataRange();
var val = ss.getDisplayValues();
var background = ss.getBackgrounds();
var fontColor = ss.getFontColors();
var fontStyles = ss.getFontStyles();
var fontWeight = ss.getFontWeights();
var fontSize = ss.getFontSizes();
var formulas = ss.getFormulas(); // Added
val = val.map(function(e, i){return e.map(function(f, j){return f ? f : getSPARKLINE(sheet, formulas[i][j])})}); // Added
return [val,background,fontColor,fontStyles,fontWeight,fontSize];
}
// Added
function getSPARKLINE(sheet, formula) {
formula = formula.toUpperCase();
if (~formula.indexOf("SPARKLINE")) {
var chart = sheet.newChart()
.setChartType(Charts.ChartType.SPARKLINE)
.addRange(sheet.getRange(formula.match(/\w+:\w+/)[0]))
.setTransposeRowsAndColumns(true)
.setOption("showAxisLines", false)
.setOption("showValueLabels", false)
.setOption("width", 200)
.setOption("height", 100)
.setPosition(1, 1, 0, 0)
.build();
sheet.insertChart(chart);
var createdChart = sheet.getCharts()[0];
var blob = createdChart.getAs('image/png');
sheet.removeChart(createdChart);
return blob;
}
}
The code that is working just for the values, which I pasted above (1st block of code), will send me an email like this:
But I need to receive the email like this, with the Sparklines below the values like so:
The code for the Email solution, just for the values, I pasted above (1st block of code) is working. But for some reason when the code from the solution linked above (2nd block of code) is imported/saved into my Google Sheets file GAS script library and adapted to my case, everything stops working, displaying the errors mentioned above.
So basically, as you might have already understood, I need to send emails with the values from Tree Average and Water Average, and I managed to get that working. But I also need for the Sparkline graphs that you can see below, and by checking my file linked below too, to also be sent as images/blobs, just below the info, like in the screenshot above.
Can anyone provide any pointers on what can be missing in applying the solution above or is there a better alternative to sending a SPARKLINE graph as image/blob by email?
Here is my file:
https://docs.google.com/spreadsheets/d/1ExXtmQ8nyuV1o_UtabVJ-TifIbORItFMWjtN6ZlruWc/edit?usp=sharing
EDIT_1:
I made some edits to bring more clarity.
EDIT_2:
As requested this is the formula applied to the first Sparkline, the 2nd one is pretty much the same:
=ARRAYFORMULA( SPARKLINE(QUERY({IFERROR(DATEVALUE(SANDBOX!$A$2:$A)), SANDBOX!$B$2:$B},
"select Col2
where Col2 is not null
and Col1 <= "&INT(MAX(SANDBOX!$A$2:$A))&"
and Col1 > "&INT(MAX(SANDBOX!$A$2:$A))-(
IFERROR(
VLOOKUP(
SUBSTITUTE($F$4," ",""),
{"24HOURS",0;
"2DAYS",1;
"3DAYS",4;
"7DAYS",8;
"2WEEKS",16;
"1MONTH",30;
"3MONTHS",90;
"6MONTHS",180;
"1YEAR",365;
"2YEARS",730;
"3YEARS",1095},
2,FALSE))
)-1, 0),
{"charttype","column";"color","#00bb21";"empty","ignore";"nan","ignore"}))
EDIT_3: At the advice of Rubén I have removed drawTable(); at the beggining of the code block.
I have also transfered the formula for the Sparkline to another helper sheet and link it to the main sheet.
After trying it seems the error does not appear anymore. Although the email received has 2 problems:
I receive the whole sheet in table form, where I just wanted the Sparklines.
Also the Sparklines do not come as images, they do not show up at all. Also where they should appear it says undefined.
I guess the whole sheet is being set because the function getting the range getDataRange(); is getting the whole sheet range.
Here is a screenshot:
As the question you reference explains:
the chart created by SPARKLINE cannot be directly imported to the email.
Why isn't the script working? Because you have not made any significant modifications to it and because you are using a more complex formula than the one proposed in the other question, it is very difficult (if not impossible) to make it work without any modifications.
What are the options? In my opinion you have 3 different options.
Follow the logic of the solution proposed by Tanaike in the other question and using EmbeddedChartBuilder try to shred the content of the FORMULA to achieve the same as with SPARKLINE.
Use the SpreadsheetApp methods to directly get the values from the sheet and build the chart from there.Here is a small example of how you can do it using Chart Service (You could achieve exactly the same with EmbeddedChartBuilder). As you already have a Blob object, you can insert it inside an email as I do inside the Sheet.
function constCreateChart() {
const sS = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('HELPER')
const chart = Charts.newDataTable()
.addColumn(Charts.ColumnType.NUMBER, '')
.addColumn(Charts.ColumnType.NUMBER, '')
// Modfify with your data
// getRange('A2:A15').getValues()...
const builder = [...Array(100).keys()].forEach(n => {
chart.addRow([n, n * n * Math.random()])
})
chart.build()
const chartShap = Charts.newColumnChart()
.setDataTable(chart)
.setLegendPosition(Charts.Position.NONE)
.setOption('hAxis.ticks', [])
.setOption('vAxis.ticks', [])
.build()
sS.insertImage(chartShap.getAs('image/png'), 5, 5)
}
Result
Use this form to request Google to add the possibility to convert charts obtained using SPARKLINES to Blob objects that can be used inside an email.
Documentation
Avalible Options in Chart Service
Fundamentals of Apps Script with Google Sheets #5:Chart and Present Data in Slides
Remove drawTable(); as this line makes that the drawTable function be executed when any function be called.
Apparently the error occurs on .addRange(sheet.getRange(formula.match(/\w+:\w+/)[0])), more specifically because formula.match(/\w+:\w+/) (this expression is intended to extract a range reference of the form A1:B10) returns null. Unfortunately the question doesn't include the formula. One possible solution might be as simple as replacing sheet.getRange(formula.match(/\w+:\w+/)[0]) by another way to set the source range for the temporary chart, but might be a more complex, i.e. adding a helper sheet to be used as the data source for the temporary chart.
NOTE: On Rev 11 one in-cell sparklines chart formula was added. As the formula is pretty complex, the simplest solution is to add a helper sheet to add the QUERY function
QUERY({IFERROR(DATEVALUE(SANDBOX!$A$2:$A)), SANDBOX!$B$2:$B},
"select Col2
where Col2 is not null
and Col1 <= "&INT(MAX(SANDBOX!$A$2:$A))&"
and Col1 > "&INT(MAX(SANDBOX!$A$2:$A))-(
IFERROR(
VLOOKUP(
SUBSTITUTE($F$4," ",""),
{"24HOURS",0;
"2DAYS",1;
"3DAYS",4;
"7DAYS",8;
"2WEEKS",16;
"1MONTH",30;
"3MONTHS",90;
"6MONTHS",180;
"1YEAR",365;
"2YEARS",730;
"3YEARS",1095},
2,FALSE))
)-1, 0)
Then instead of sheet.getRange(formula.match(/\w+:\w+/)[0]) use helperSheet.getDataRange(). You will have to set an appropriate way to declare helperSheet.
Related to Rev. 8
The code on Tanaike's answer reads data from Sheet1 but your sheet is named SANDBOX.
Javascript newbie here, so apologies if this is incredibly basic and not the prettiest coding.
I'm creating a form that I want to have sent to a different email address depending on which team someone selects (e.g. if you select Sales it will generate an email to Team X, Refunds will email Team Y etc).
So far I've got to the stage where I can click a button to generate an email and attach the form as a PDF, but I don't know how to make it variable depending on the field value.
Current code:
var callerName = this.getField("CallerName").value;
var customSubject = this.getField("WhichTeam").value;
//I've used a fake email address for the next line variable, but this is the one I want to change depending on the "WhichTeam" field value
var mailtoUrl = "mailto:Email#email.com?subject=Callback Referral for " + customSubject;
this.submitForm({
cURL: mailtoUrl,cSubmitAs: "PDF"});
Hope this makes sense. Grateful for any help/advice?
Thanks
You can try using an object to contain the emails with the teams as they values. And to also use a template string to insert the value into the mailtoUrl.
var emails = {
refunds: 'refundsTeamEmail',
sales: 'salesTeamEmail',
}
var callerName = this.getField("CallerName").value;
var customSubject = this.getField("WhichTeam").value;
// ex. customSubject value is 'refunds'
// emails[customSubject] would be the same as doing emails.refunds
// which return the value of 'refundsTeamEmail'
var mailtoUrl = `mailto:${emails[customSubject]}?subject=Callback Referral for ` + customSubject;
this.submitForm({
cURL: mailtoUrl,cSubmitAs: "PDF"});
I think this is the simplest way to do it as the value will change each time you run the function to send the email without having to create or call other functions.
Having just said the last thing, you could also use a switch case in another function to return the value. If you want to. Something like:
function fetchEmail(email) {
switch(email) {
case 'refunds':
return 'refundsTeamEmail'
case 'sales':
return 'salesTeamEmail'
default:
return ''
}
}
var callerName = this.getField("CallerName").value;
var customSubject = this.getField("WhichTeam").value;
// ex. customSubject value is 'refunds'
// emails[customSubject] would be the same as doing emails.refunds
// which return the value of 'refundsTeamEmail'
var mailtoUrl = `mailto:${fetchEmail(customSubject)}?subject=Callback Referral for ` + customSubject;
this.submitForm({
cURL: mailtoUrl,cSubmitAs: "PDF"});
I am new to Google Apps Script. I have a sheet that collects some "Order Number" from form submit. I want to send mails through an event (On form submit) from my spreadsheet. The form will serve an order number. When the form is submitted, it will match the older submitted order numbers throughout the whole column. If it got matched once, the mail won't be sent. If it doesn't match then it will send a mail to the email address next to the order number.
The email address will come from another sheet on the same spreadsheet using VLOOKUP. I managed to do this.
Sorry if I make any mistake with my English.
Edit:
I tried map() , filter() , indexOf() these methods. But I too new with this.
function search(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Copy of orderStatus");
var lr = ss.getLastRow() - 1;
var keyword = ss.getRange("H5").getValue();
var dataSource = ss.getRange(2, 2, lr, 1).getValues();
var mapped = dataSource.map(function(r){
return r[0]});
var showPos = mapped.indexOf(keyword) + 2;
var getMail = ss.getRange(showPos, 4).getValue();
var filted = mapped.filter(filterlogic);
}
var filterlogic = function(r){
if(r !== "zil20200010"){
return true;
} else {
return false;
}
}
On form submit, select the column (range) where you store all the order numbers and create a TextFinder and store it in a variable using the createTextFinder(findText) method for the specified range.
Get the TextFinder from the previous step and search the order number using the findNext() method.
If findNext() returns null then move to the next step. else, do nothing.
Get the email address to which you plan to send the order number.
After having the email address, use the sendEmail(recipient, subject, body, options) method to send the email. If you'd like, you can use HTML in the body to make it more professional.
For additional information, read:
the reference guide on creating TextFinders,
the reference guide on finding text using a TextFinder,
and the reference guide on GmailApp.
Sample code:
// imagine you store all the order numbers in column C, starting from row 2 to the last row in the column:
var emailRecipient = test#test.com;
var ordernumber = 123;
var RangeToSearch = sheet.getRange(2,3,sheet.getLastRow());
var TextFinder = RangeToSearch.createTextFinder(ordernumber);
var found = TextFinder.findNext();
if (found == null) {
MailApp.sendEmail({
to: emailRecipient,
subject: "New Order! Order Number: " + ordernumber,
htmlBody: html
});
}
First of all, thanks to all of you who helped me to reach this point. I found the solution to my problem after some "trial and error". I wanted to limit sending emails.
This code takes the Range. Get its values in an array. I mapped that array to act as a string. Then I added .pop() to that string, it removes our last/newly submitted data in that range. Then I used .includes() method to search my value in the mapped array, and assigned it to a variable called final (just came to my mind). This variable returns true/false depending on search results. If the order number does not exist then it returns false. After that, we set an if statement to execute our mailing function. If our order number does not match and return final as false our mailing function happens. Else it does nothing (means no email sents). And that's it!
Here is the code that solved my problem
function orderStatus(e) {
try {
var theirMail, subject, message;
var ourName, theirName;
var sSheet, orderNum, cosmetics, orderSts, phNum, lr,dataSource, mapped, final;
ourName = "My Company Name";
orderNum = e.namedValues["Order Number"].toString();
sSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("orderStatus");
lr = sSheet.getLastRow() - 1;
dataSource = sSheet.getRange(2, 2, lr).getValues();
mapped = dataSource.map(function(r){
return r[0].toString()});
mapped.pop();
final = mapped.includes(orderNum);
orderSts = sSheet.getRange(sSheet.getLastRow(),1).getValue();
theirMail = sSheet.getRange(sSheet.getLastRow(),4).getValue();
theirName = sSheet.getRange(sSheet.getLastRow(),5).getValue();
phNum = sSheet.getRange(sSheet.getLastRow(),6).getValue();
subject = "Order status notification from " + ourName + " to " + theirName;
if (final == false){
message =
"<div style='text-align: left; padding-left: 30px;'><h2>Dear <b>" + theirName +
",</b></h2><p>Your order no is <b><span style='font-size: 14px;'>" + orderNum +
"</span>.</b> <b><span style='font-size: 14px;'>Your order has been processed.</span>" +
"</b></p><p>We packaged your order and dropped it to the logistics. You will recieve phone call on <b><span style='font-size: 14px;'>" + phNum +
"</span></b> from logistics.<br>Thanks for purchasing from <b><span style='font-size: 14px;'>" + ourName +
"</span></b>.</p><p>Best regards,<br><b><span style='font-size: 14px;'>"+ourName+"</span></b></p></div>"+
"<p style='text-align: center;'><br><b>For further information please visit our facebook page <a href='https://www.facebook.com/' target='_blank' rel='noopener'>"+ourName+"</a>.</b></p><hr />";
textbody = message.replace("<br>", "\n\n");
cosmetics = {name: ourName, htmlBody: message};
MailApp.sendEmail(theirMail, subject, message, cosmetics);
}
}
catch (e) {
Logger.log(e.toString());
}
}
I would like to extract all links from a folder in google drive and write two columns, one containing the link, and the next column containing the file name.
I'm new to google scripting and wrote this code:
function myFunction() {
var ss=SpreadsheetApp.getActiveSpreadsheet();
var s=ss.getActiveSheet();
var c=s.getActiveCell();
var fldr=DriveApp.getFolderById("0B37vVx5p-eGMTmJmTF9JOUwxZnc");
var files=fldr.getFiles();
var names=[],f,str;
while (files.hasNext()) {
f=files.next();
str='=hyperlink("' + f.getUrl() + '")';
names.push([str]);
}
s.getRange(c.getRow(),c.getColumn(),names.length).setFormulas(names);
}
function myFunction() {
var ss=SpreadsheetApp.getActiveSpreadsheet();
var s=ss.getActiveSheet();
var c=s.getActiveCell();
var fldr=DriveApp.getFolderById("0B37vVx5p-eGMTmJmTF9JOUwxZnc");
var files=fldr.getFiles();
var names=[],f,str;
while (files.hasNext()) {
f=files.next();
str='=" + f.getName() + "';
names.push([str]);
}
s.getRange(c.getRow(),c.getColumn(),names.length).setFormulas(names);
}
The problem is that after writing the links in the first column, it overwrites it with the names in the first column. How can I specify what column the second piece of code should be written to?
There's no real reason to grab these in separate functions, you could just push them both to the same array then insert into the sheet, the code below should do what you're expecting:
function getFolders() {
var ss=SpreadsheetApp.getActiveSpreadsheet();
var s=ss.getActiveSheet();
var c=s.getActiveCell();
var fldr=DriveApp.getFolderById("0B37vVx5p-eGMTmJmTF9JOUwxZnc");
var files=fldr.getFiles();
var names=[],f,url,name;
while (files.hasNext()) {
f=files.next();
url='=hyperlink("' + f.getUrl() + '")';
name='="' + f.getName() + '"';
names.push([url,name]);
}
s.getRange(c.getRow(),c.getColumn(),names.length, 2).setFormulas(names);
}
I had to tweak name='="' + f.getName() + '"'; because the quotes weren't quite in the right place, it was pushing that string itself to the array rather than the filename.
Pushing both of the values to the array like this means you don't need to offset the range because the array will span 2 columns when you use setValues(), hence why I had to add 2 for numColumns in the setValues() too.
On a Google Apps Script project each function should have unique names otherwise only the last function in the order that they are loaded at execution time will be executed. So, in first place, the hyperlink formulas aren't never written.
By the other hand, as was mentioned on a previous answer, there is no need to have separate functions to write on each column, one single function could fill both columns at the same time.
On this code line:
names.push([str]);
The argument por push is an array having a single value. Replace this argument by an array incluiding the values for both columns. To make the minimum changes to you first function, rename the str variable, add a second code line for the second column with a proper and unique variable name , i.e. (as was shown on the previous answer):
url='=hyperlink("' + f.getUrl() + '")';
name='=" + f.getName() + "';
names.push([url,name]);
And lastly, add the number of columns to the last statement
s.getRange(c.getRow(),c.getColumn(),names.length, 2).setFormulas(names);
Don't forget to delete the second function named myFunction
Use Range.offset:
Returns a new range that is offset from this range by the given number of rows and columns (which can be negative). The new range is the same size as the original range.
I want to create a dynamic function to INSERT data into the webSQL Database. I cannot use indexed DB because Zetakey does not support it.
tx.executeSql("INSERT INTO " + table + "(" + formatfields + ")
VALUES (" + formatqm + ")",
[formatvalues],
webdb.onSuccess,
webdb.onError);
Ich übergebe an den Query:
formatfields = "one, two"; (up to an undefined number)
formatqm = "?, ?";
formatvalues = "123, 456"; (dynamic user entries for x fields)
Can someone tell me what do I have to do with the formatvalues? When I write 123, 456 directly its working fine.
Thanks in advance!
Instead of dynamically create or change table column fields, use JSON serialization of the record. Basically store stringify user given object data on INSERT and parse on retrieval. If you need query over column, initialize those columns only. It will be just like IndexedDB does.
/*array.push */
formatvalues = new Array;
formatvalues.push("123");
and so on!