Recalling function values in prompt text - google-apps-script

I hope you'll forgive me if I have no clue what I'm saying as I've had no real instruction on how to write code. However, I will do my best to explain it well if you will bear with me.
Moving forward, I'm looking to create a prompt that appears upon opening a Google Sheet that will display both a line of text and a line of character values from a function. I may be doing this the most difficult way possible but this is all I have so far:
SpreadsheetApp.getUi().alert('Text goes here' return function lookup(today(),A1:B6));
I'm sure this looks like utter nonsense, but that's why I'm here! I need to be able to recall the contains of the first cell within a row that contains today's date. That is, if a sheet looks something like this:
ABC 3/21 3/28 4/1 4/4 4/7
DEF 3/20 3/29 4/2 4/5 4/9
I need it to read "Text goes here ABC" on the 4th of April and "Text goes here DEF" on the 5th of April.
I hope that makes sense. Please let me know if you need additional information! And I appreciate any help that is offered.

Great question. So first of all, I would recommend you check out Browser.msgBox() (documentation here).
So this isn't super easy, so it may take some work to understand what's going on here. But I created a sheet that works for this. Here's the code:
function onOpen() {
// get the values from the spreadsheet
var ss = SpreadsheetApp.getActiveSheet();
vals = ss.getRange(1, 1, ss.getLastRow(), ss.getLastColumn()).getValues();
// get a greeting based on the date
var greeting = greetingsForDate(vals, formatDate(new Date()));
// display the greeting
Browser.msgBox("Text goes here " + greeting)
}
function greetingsForDate(vals, dateString) {
var greetings = []
// loop through each row
for(var rowNum = 0; rowNum < vals.length; rowNum++) {
rowContent = vals[rowNum];
// loop through each column
for(var columnNum = 1; columnNum < rowContent.length; columnNum++) {
var cellVal = rowContent[columnNum];
// return the value from column A if the current date is in the row
if(typeof cellVal === "object" && formatDate(cellVal) === dateString) {
greetings.push(rowContent[0]);
}
}
}
if(greetings.length === 0) {
return "(none)";
} else {
return greetings.join(", ");
}
}
// returns a formatted date like 04/02/14
function formatDate(date) {
return Utilities.formatDate(date, Session.getScriptTimeZone(), "MM/d/y");
}
It assumes the sheet is structured like this:
ABC 3/21/2014 3/28/2014 4/1/2014 4/4/2014 4/7/2014
DEF 3/20/2014 3/29/2014 4/2/2014 4/5/2014 4/9/2014
If this code doesn't make whole lot of sense, I would highly recommend checking out a site like codecademy.com or codeschool.com. Codecademy's JavaScript course is especially good.
Good luck!
EDITS to return multiple values and check that cells are dates as per the comments.

Related

Using for and if loops in Google Apps Script

Dear programming Community,
at first I need to state, that I am not quite experienced in VBA and programming in general.
What is my problem? I have created a topic list in google sheets in order to collect topics for our monthly meeting among members in a little dance club. That list has a few columns (A: date of creation of topic; B: topic; C: Name of creator; ...). Since it is hard to force all the people to use the same format for the date (column A; some use the year, others not, ...), I decided to lock the entire column A (read-only) and put a formular there in all cells that looks in the adjacent cell in column B and sets the current date, if someone types in a new topic (=if(B2="";"";Now()). Here the problem is, that google sheets (and excel) does then always update the date, when you open the file a few days later again. I tried to overcome this problem by using a circular reference, but that doesn't work either. So now I am thinking of creating a little function (macro) that gets triggered when the file is closed.
Every cell in Column B (Topic) in the range from row 2 to 1000 (row 1 is headline) shall be checked if someone created a new topic (whether or not its empty). If it is not empty, the Date in the adjacent cell (Column A) shall be copied and reinserted just as the value (to get rid of the formular in that cell). Since it also can happen, that someone has created a topic, but a few days later decides to delete it again, in that case the formular for the date shall be inserted again. I thought to solve this with an If-Then-Else loop (If B is not empty, then copy/paste A, else insert formula in A) in a For loop (checking rows 1 - 1000). This is what I have so far, but unfortunately does not work. Could someone help me out here?
Thanks in advance and best regards,
Harry
function NeuerTest () {
var ss=SpreadsheetApp.getActive();
var s=ss.getSheetByName('Themenspeicher');
var thema = s.getCell(i,2);
var datum = s.getCell(i,1);
for (i=2;i<=100;i++) {
if(thema.isBlank){
}
else {
datum.copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}}
}
The suggested approach is to limit the calls to the Spreadsheet API, therefore instead of getting every cell, get all the data at once.
// this gets all the data in the Sheet
const allRows = s.getDataRange().getValues()
// here we will store what is written back into the sheet
const output = []
// now go through each row
allRows.forEach( (row, ind) => {
const currentRowNumber = ind+1
// check if column b is empty
if( !row[1] || row[1]= "" ){
// it is, therefore add a row with a formula
output.push( ["=YOUR_FORMULA_HERE"] )
} else {
// keep the existing value
output.push( [row[0]] )
}
})
Basically it could be something like this:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Themenspeicher');
var range = sheet.getRange('A2:B1000');
var data = range.getValues(); // <---- or: range.getDisplayValues();
for (let row in data) {
var formula = '=if(B' + (+row+2) + '="";"";Now())';
if (data[row][1] == '') data[row][0] = formula;
}
range.setValues(data);
}
But actual answer depends on what exactly you have, how your formula looks like, etc. It would be better if you show a sample of your sheet (a couple of screenshots would be enough) 'before the script' and 'after the script'.

Better/faster way to pass 50+ values from one Google sheet to another

I'm brand new to App Script, so please forgive my ignorance.
The Google sheet I use to hold student data is so long and unwieldy (50+ columns) that I decided to create another sheet to act as a front-end for data entry. Through hours of tutorial videos + bumbling trial and error, I've come up with a working script that takes values from my data entry form-like sheet ('Students') and passes those values to the first empty row in my destination/container sheet ('Master').
I'm really pleased with how the script working - except for the fact that it is ridiculously slow. Based on what I've read, I think I'm making too many calls to the Sheets API, and I need to figure out how to pass all the values from 'Students' to 'Master' en masse rather than one-by-one, but I don't have the skills to do that, and I can't seem to find an example.
I'm sure there's a really simple, elegant solution. Can anyone help?
Here's a little piece of my code (hopefully it's enough to see the inefficient strategy I'm using):
function submitStudentData(){
var caseloadManager = SpreadsheetApp.getActiveSpreadsheet();
var enterStudents = caseloadManager.getSheetByName('Students');
var masterSheet = caseloadManager.getSheetByName('Master');
var clearFields = enterStudents.getRangeList(['C6:C18', 'C22', 'E6:E18','G6:G14','G20','I6:I14','K6:K16', 'M6:M18']);
var blankRow = masterSheet.getLastRow()+1;
masterSheet.getRange(blankRow,1).setValue(enterStudents.getRange("Z1").getValue()); //Concatenated Student Name
masterSheet.getRange(blankRow,3).setValue(enterStudents.getRange("C6").getValue()); //First Name
masterSheet.getRange(blankRow,2).setValue(enterStudents.getRange("C8").getValue()); //Last Name
masterSheet.getRange(blankRow,4).setValue(enterStudents.getRange("C10").getValue()); //Goes By
masterSheet.getRange(blankRow,6).setValue(enterStudents.getRange("E6").getValue()); //DOB
masterSheet.getRange(blankRow,7).setValue(enterStudents.getRange("E8").getValue()); //Grade
masterSheet.getRange(blankRow,5).setValue(enterStudents.getRange("E10").getValue()); //Student ID
masterSheet.getRange(blankRow,10).setValue(enterStudents.getRange("E14").getValue()); //Last FIE
masterSheet.getRange(blankRow,11).setValue(enterStudents.getRange("Z2").getValue()); //Calculated FIE Due Date
masterSheet.getRange(blankRow,8).setValue(enterStudents.getRange("E12").getValue()); //Last Annual Date[enter image description here][1]
masterSheet.getRange(blankRow,13).setValue(enterStudents.getRange("G6").getValue()); //PD
masterSheet.getRange(blankRow,14).setValue(enterStudents.getRange("G8").getValue()); //SD
masterSheet.getRange(blankRow,15).setValue(enterStudents.getRange("G10").getValue()); //TD
masterSheet.getRange(blankRow,16).setValue(enterStudents.getRange("G3").getValue()); //Concatenated Disabilities
masterSheet.getRange(blankRow,18).setValue(enterStudents.getRange("G12").getValue()); //Program Type
masterSheet.getRange(blankRow,12).setValue(enterStudents.getRange("G14").getValue()); //Evaluation Status
masterSheet.getRange(blankRow,20).setValue(enterStudents.getRange("I6").getValue()); //DYS
masterSheet.getRange(blankRow,21).setValue(enterStudents.getRange("I8").getValue()); //GT
masterSheet.getRange(blankRow,19).setValue(enterStudents.getRange("I10").getValue()); //EB
masterSheet.getRange(blankRow,24).setValue(enterStudents.getRange("I12").getValue()); //ESY
masterSheet.getRange(blankRow,22).setValue(enterStudents.getRange("I14").getValue()); //BIP
masterSheet.getRange(blankRow,29).setValue(enterStudents.getRange("K6").getValue()); //TR
masterSheet.getRange(blankRow,30).setValue(enterStudents.getRange("K8").getValue()); //OT
It goes on and one like this for 52 values before clearing all the fields in 'Students.' It works, but it takes well over a minute to run.
I'm trying to attach a picture of my 'Students' form-like sheet in case my description isn't clear.
Thanks so much for helping a humble special educator who knows not what she's doing. :)
Image of 'Students' form/sheet
Read best practices Even though your data isn't a contiguous range it is part of one so get the whole range with getValues() and use the appropriate indices to access the ones that you want. In the end if will be much faster. You may not want to use setValues to write the data because of other issues like messing up formulas. Avoid the use of setValue() and getValue() whenever possible
function submitStudentData() {
const ss = SpreadsheetApp.getActive();
const ssh = ss.getSheetByName('Students');
const msh = ss.getSheetByName('Master');
const nr = msh.getLastRow() + 1;
const vs = ssh.getRange(nr, 1, ssh.getLastRow(), ssh.getLastColumn()).getValues();
let oA1 = [[vs[0][25], vs[7][2], vs[5][2], vs[9][2], vs[9][4], vs[5][4], vs[7][4], vs[11][4]]];
msh.getRange(msh.getLastRow() + 1, 1, oA1.length, oA[0].length).setValues(oA1);//This line replaces all of the below lines
msh.getRange(nr, 1).setValue(vs[0][25]);//Concatenated Student Name
msh.getRange(nr, 2).setValue(vs[7][2]); //Last Name
msh.getRange(nr, 3).setValue(vs[5][2]); //First Name
msh.getRange(nr, 4).setValue(vs[9][2]); //Goes By
msh.getRange(nr, 5).setValue(vs[9][4]); //Student ID
msh.getRange(nr, 6).setValue(vs[5][4]); //DOB
msh.getRange(nr, 7).setValue(vs[7][4]); //Grade
msh.getRange(nr, 8).setValue(vs[11][4]); //Last Annual Date[enter image description here][1]
You could also do a similar thing by using formulas to map all of the data into a single line or column making it much easier to run the scripts.
Here is the working example. Just complete the mapping array as desrbied in the code. The runtime is below 1 second.
const mapping= [
// enter the array [ sourceRange, destinationRow ] for each cell you want to copy form Students to Master
['Z1',1],
['C6',3],
['C8',2],
['C10',4],
['E6',6]
// ... and so on
]
function submitStudentData() {
console.time('submitStudentData')
const caseloadManager = SpreadsheetApp.getActive();
const enterStudents = caseloadManager.getSheetByName('Students');
const masterSheet = caseloadManager.getSheetByName('Master');
const data = enterStudents.getDataRange().getValues()
const destRow = []
mapping.forEach((m,i)=>{
[rowi,coli] = rangeToRCindex(m[0])
const destRowIndex = m[1] - 1
destRow[destRowIndex] = data[rowi][coli]
})
masterSheet.appendRow(destRow)
console.timeEnd('submitStudentData')
}
function rangeToRCindex(range){
const match = range.match(/^([A-Z]+)(\d+)$/)
if (!match) {
throw new Error(`invalid range ${range}`)
}
const col = letterToColumn(match[1])
const row = match[2]
return [row-1,col-1]
}
function letterToColumn(columnLetters) {
let cl = columnLetters.toUpperCase()
let col = 0
for (let i = 0; i < cl.length; i++) {
col *= 26
col += cl.charCodeAt(i) - 65 + 1
}
return col
}
As Cooper said you want to avoid reading and writing to the sheet(s) as much as possible. (I had the same issue when I started with Google Script)
This means that you should read the whole range into a variable and then write your rows out to the master sheet.
Below is an example of what you could use to avoid the setValue() and getValue() slowness you are experiencing
function submitStudentData(){
var caseloadManager = SpreadsheetApp.getActiveSpreadsheet();
var enterStudents = caseloadManager.getSheetByName('Students');
var masterSheet = caseloadManager.getSheetByName('Master');
var clearFields = enterStudents.getRangeList(['C6:C18', 'C22', 'E6:E18','G6:G14','G20','I6:I14','K6:K16', 'M6:M18']);
var blankRow = masterSheet.getLastRow()+1; //You will not need this
//First we will all the data from the students sheet. This will make and array of arrays [[row],[row],[row]].
studentData = enterStudents.getRange(1,1,enterStudents.getLastRow(),enterStudents.getLastColumn()).getValues()
Logger.log(studentData)
//We are going to build an array of arrays of the data that we want to write back to the master sheet. We will start by creating our first array
writeData = []
//Then we loop through all the student data
for (var i = 0; i < studentData.length; i++) {
Logger.log(studentData[i][0])
//We are selecting data from each row to add to our array. in "studentData[i][0]" the [0] is the column number (remember we are starting with 0)
rowData = []
rowData.push(studentData[i][0])
rowData.push(studentData[i][2])
rowData.push(studentData[i][1])
//Then we send the full row to the first array we made
writeData.push(rowData)
}
Logger.log(writeData)
// Now to write out the data. Normally it would not be a good idea to loop a write like this but this as an atomic operation that google will automatically batch write to the sheet.
for (var i = 0; i < writeData.length; i++) {
masterSheet.appendRow(writeData[i])
}
}
Hope this helps get you started.

Can't seem to get any Google Scripts code to work to BOLD all occurrences of one specific word

I found that RichTextValue should work, but I'm not sure where to put the word in on this code and when I use this code
function Bold() {
for (var i = 4; i <=160; i++){
var cell = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(i, 1);
cell.setRichTextValue(SpreadsheetApp.newRichTextValue()
.setText(cell.getValue())
.setTextStyle(2, 16, SpreadsheetApp
.newTextStyle()
.setBold(true)
.build())
.build());
}
}
I get an illegal argument error and I have no idea where I am going wrong. I am not a developer but got pretty good at VBA. Unfortunately I'm really battling with Google Scripts. Any help would be appreciated as this was the only thing I could not get right. Thank you!
Let's consider the following code:
function testBold() {
var boldStyle = SpreadsheetApp.newTextStyle().setBold(true).build();
var cells = SpreadsheetApp.getActive().getRange('Лист1!A1:A100');
var values = cells.getValues(); // initial values
var richValues = values.map( // become rich values
function(v) {
var richBuilder = SpreadsheetApp.newRichTextValue().setText(v[0]);
if (v[0].length && v[0].length > 16) richBuilder = richBuilder.setTextStyle(2, 16, boldStyle);
return [richBuilder.build()];
}
);
cells.setRichTextValues(richValues);
}
At first, we avoid to read data from each cell sequencially, because there is more powerful way to read range data at once.
Further, we use the power of JS map() function to obtain desired rich styling for each input values array element. Do not remember to check input values to have string type and sufficient length before applying the style.
At last we write rich-style data back to the same sheet range.

I have a pseudocode, but can't code yet

thank you and sorry for my incredibly unexperienced question in advance. So, I want to make a code and I know what I want it to do, I just don't know how to program. What I need is:
function GenPre()
1.- delete range Presupuesto!A12:C42
2.- copy range Imp!A2:Imp!C33 VALUES in Presupuesto!A12:Presupuesto!C42 (Imp cells are formulas, and I want to copy just the values)
3.- show only used rows in column A in Presupuesto!A12:A42 (consider some rows will be already hidden, so unhiding them first would be an idea)
4.- go to sheet Presupuesto (once I do this function, I want to end up on the sheet Presupuesto
end Generar
This function will be runned by a button in another sheet in the same spreadsheet.
and so far, I have this:
function GenPre() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetbyname(Presupuesto);
//next step is to select and delete the content of the range on the sheet
}
I know I'm asking for much, I just can't find much about selecting defined cells... and I really don't know how to program yet.
Thanks a bunch!!
Edit
So, I started tweaking with what k4k4sh1 answered and got this (AND reading other posts on hiding rows containing "x" on a given cell):
function GenPre() {
var sheetp = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Presupuesto') //name a variable to the sheet where we're pasting information
var sheetc = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Imp') //name a variable to the sheet frome where we're copying information
sheetp.getRange('a12:c41').clearContent() //delete all values in the range where we're copying
sheetc.getRange('A2:C31').copyValuesToRange(sheetp,1,3,12,41); //copy from source range to destination range
sheetp.showRows(12,41); //make sure all rows in the destination range are shown
for( i=12 ; i<=41 ; i++) {
if (sheetp.getRange('A'+i).getValue() == '') { // status == ''
sheetp.hideRows(i);
}
}
}
Te script is running how it should, but now, I want it to run faster (takes 12 seconds to run, when it doesn't really look that heavy), and is there a function to switch my view to sheetp? thank you all!
You're asking us to do all the work :)
Let's start from your piece of code:
the method .getSheetByName(shName) accepts a string as argument, so you should change it to
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Presupuesto');.
Mind that JavaScript is case-sensitive, so .getSheetbyname is not the same as .getSheetByName().
According to Sheet Class Reference use sheet.getRange() to get your Range Object. Take a look to Range Class Reference: to clear the range content including formats use .clear(), to clear just the content leaving the formatting intact use .clearContent().
To hide unused rows try:
function hideRows(sheetName, column) {
var s = SpreadsheetApp.getActive().getSheetByName(sheetName);
s.showRows(1, s.getMaxRows());
s.getRange(column)
.getValues()
.forEach(function (r, i) {
if (r[0] == '') {s.hideRows(i + 1);}
});
}
// hideRows('Presupuesto', 'A12:A42');

If statement in for loop referencing incremented variable

as quite an novice when it comes to coding, I ran into this problem with my code:
I use Google App Script [Edit: Corrected Google App Engine to Google App Script] to go through a list of timestamps and filter for stamps that equal the current month. Therefor I load the spreadsheet, the according sheet and get the data from all the rows as an object.
In the next step I go though all the elements of the object and check whether they contain the current date.
/* Initial data */
var email = "name#domain.com";
var spreadsheet = SpreadsheetApp.openById("1bN7PTOa6PwryVvcGxzDxuNVkeZMRwYKAGFnQvxJ_0nU");
var tasklist = spreadsheet.getSheets()[0].getDataRange();
var tasks = tasklist.getValues();
var tasksnum = tasklist.getNumRows();
Logger.log(tasks[7][2]); //Console returns "01.12.2014"
Logger.log(tasks[7][2].indexOf(month)); //Console returns "12.2014"
/* Filter tasks by month */
for (var i = 1; i < 9; i++) {
if (tasks[i][2].indexOf(month) >= 0) {
Logger.log(tasks[i]);
}
else {
return;
}
}
What drives me crazy is the following: As stated above, the for loop doesn't work. But if I alter it like this
if (tasks[7][2].indexOf(month) >= o) {
it works like a charm. And that's what I don't get. i should be incremented til 9 so should be seven at some point. At least then, the condition should be true and the loop should return a log.
What am I missing?
Thank you all in advance.
ps: If I am just following the wrong path of how to implement the function, please let me know.
ps2: I think my question's title is a bit cryptic. If you have a better one in mind, I'd love to change it.
The reason your original code doesn't work is due to the return statement in your else block. Return exits the function immediately, so your loop never gets past the first "false" of your IF.
remove the "return" (or the else block entirely) and your code should work.
I'm not sure what's supposed to be in the month variable but I suggested building a string to match your date format and doing string comparisons instead of the indexOf() strategy you're trying. This code is tested and does just that:
function test() {
/* Initial data */
var spreadsheet = SpreadsheetApp.openById("");
var tasklist = spreadsheet.getSheets()[0].getDataRange();
var tasks = tasklist.getValues();
var tasksnum = tasklist.getNumRows();
/* Filter tasks by month */
var today = Utilities.formatDate(
new Date(),
spreadsheet.getSpreadsheetTimeZone(),
"dd.MM.yyyy"
);
for(var i=1; i<9; i++){
if (tasks[i][2] == today){
Logger.log(tasks[i]);
}
}
}