I have a pseudocode, but can't code yet - google-apps-script

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

Related

How to tell a script which row to start output

I am using a script to output the date and time that a row was last updated on a Google Sheet. It seems to work just fine, but I want it to only output beginning at the second row, since my first row is a header row full of labels for the columns. I can't seem to figure it out.
The script is from here: https://www.wikihow.com/Google-Sheets-How-to-Insert-Time-in-Cell-Automatically#Script-Editor
This is how it looks in my Apps Script:
/** #OnlyCurrentDoc */
function onEdit(e){
const sh = e.source.getActiveSheet();
sh.getRange ('A' + e.range.rowStart)
.setValue (new Date())
.setNumberFormat ('MM/dd/yyyy HH:MMam/pm');
}
I think I need to define rowStart, but I'm not sure how to do that and search engines haven't pulled up answers I understand.
I tried appending rowStart(2) and rowStart > 0 in place of the original rowStart, which produced errors and made the whole script stop working. I also tried the below with the same response.
rowStart(
value : 2
) : 2;
From here: https://help.grapecity.com/spread/SpreadJSWeb/JavascriptLibrary~GcSpread.Sheets.PrintInfo~rowStart.html
I am new to Google Apps Script (and any scripting), though a longtime Excel and Sheets user. So please explain it to me like I am five. 😅 Branching out!
OK, so this script, as it says on the original wikihow page, will:
insert a timestamp into the specified column any time you enter data into a cell, in the same row as the data you entered. For instance, if you type something into cell A2, a timestamp will appear in cell M2.
So essentially the way that script works, at least per my reading:
It is defining an onEdit handler which handles an edit event every time a user makes any change to any cell in the spreadsheet
the edit event (called e in the script) has a bunch of information about the edit on it, including the row number of the first row that was edited (e.range.rowStart)
the script contains a hard-coded column letter (in your case, A, in the example on wikihow they used M)
every time any edit is made , it goes to the cell identified by the hardcoded column letter and the 1st row that was edited, and it inserts the current date into that cell.
First of all, since this is JavaScript, you can paste your code into a text editor that supports JavaScript, like VSCode for example, and ask it to format it for you. This might bring some clarity to the code because the text editor knows what the syntax means, so it can give you some hints about whats going on or at very least it can make it look nicer:
/** #OnlyCurrentDoc */
function onEdit(e) {
const sh = e.source.getActiveSheet();
sh.getRange('A' + e.range.rowStart)
.setValue(new Date())
.setNumberFormat('MM/dd/yyyy HH:MMam/pm');
}
Second of all, single letter variable names and abbreviations are often frowned upon because they can prevent us from knowing what's going on. And sometimes code can be clarified by giving names to things that might otherwise not be obvious at all. So I will do some naming in this code:
/** #OnlyCurrentDoc */
function onEdit(editEvent) {
const sheet = editEvent.source.getActiveSheet();
const rowThatWasEdited = editEvent.range.rowStart;
const columnThatHoldsTheLastEditedDates = 'A';
const correspondingCell = columnThatHoldsTheLastEditedDates + rowThatWasEdited;
const rightNowDate = new Date();
sheet.getRange(correspondingCell)
.setValue(rightNowDate)
.setNumberFormat('MM/dd/yyyy HH:MMam/pm');
}
Finally, since this is javascript, we have access to all of the javascript features like if statements, loops, functions, etc. Since you want to make it ignore the header row, I would recommend an if statement: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else
if we are on the header row, do nothing. Otherwise, do the normal thing.
/** #OnlyCurrentDoc */
function onEdit(editEvent) {
const sheet = editEvent.source.getActiveSheet();
const rowThatWasEdited = editEvent.range.rowStart;
if(rowThatWasEdited > 1) {
const columnThatHoldsTheLastEditedDates = 'A';
const correspondingCell = columnThatHoldsTheLastEditedDates + rowThatWasEdited;
const rightNowDate = new Date();
sheet.getRange(correspondingCell)
.setValue(rightNowDate)
.setNumberFormat('MM/dd/yyyy HH:MMam/pm');
}
}

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'.

Changing info on a different sheet in the same spreadsheet

I have two ranges of equal size on different sheets in the same spreadsheet. I am trying to find a row (based off of user input) in the first sheet and then use that index to modify a table in the second sheet that counts how many times that certain index has been used before (to make a nice looking pie chart).
This code runs but will not produce results on the second sheet. I've gone through the debugging process and my best guess is that for some reason, my for in loop is not running through. Attached is my code that takes in the beforementioned index and attempts to perform the second half of my goal.
function acceptToEncounterChart(ghostrow) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
SpreadsheetApp.setActiveSheet(ss.getSheets()[1]);
ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Average Encounter Chart");
var range = sheet.getRange("B3:B14")
for(var i in range) {
if(ghostrow == i) {
var before = range[i][0].getValue()
range[i][0].setValue(before + 1);
}
}
SpreadsheetApp.setActiveSheet(ss.getSheets()[0]);
};
Explanation:
I am not entirely sure what is your goal.
However, here is some fixes / improvements starting from the beginning:
You define 2 times the same variable ss with exactly the same value.
You don't need to set the active sheet, if your goal is to just get the sheet, therefore this line is redundant:
SpreadsheetApp.setActiveSheet(ss.getSheets()[1]);
Variable range is not an array but a range object. You can't index it and therefore you can't also use a for loop to iterate over a single object. For the same exact reason, the code inside the if statement is wrong, you can't index range. But you don't see any errors because the if statement evaluates to false.
In JavaScript and in many other programming languages, array indexes start from 0. Since your range starts from cell B3 or row 3, you need to use i+3 to match the data with the range.
For the same reason as the previous point, ghostrow is an index, not a row. The if statement compares an array index i with ghostrow, so ghostrow should not be confused with the actual sheet row. For example, if you choose ghostrow=5 then the current script will increment the value of the cell B8 (remember i+3) by 1.
Solution:
Here is a workable code snippet:
function acceptToEncounterChart(ghostrow) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Average Encounter Chart");
var data = sheet.getRange("B3:B14").getValues().flat();
data.forEach((v,i)=>{
if(ghostrow == i){
sheet.getRange(i+3,2).setValue(v+1)
}
});
ss.setActiveSheet(ss.getSheets()[0]);
}
Related:
Please explore the official google apps script documentation.

Get Range from GetRangeByName, activate range

My goal is to automate adding 10 rows to a Google Sheet used by a nonprofit organization's business and then replicate the formulas sequences needed in the newly created rows. I had all the code working to accomplish the task and prevent users from messing up the spreadsheet formulas when they manually insert rows. However, the code time out due to the number of rows in the spreadsheet with the looping use of getRange(). My new approach is to jump to a named cell as a starting point instead of the the really slow looping cell search.
I have created a name "EndData", read all the stuff I can find online, trialed and errored the syntax for hours to get the named_cell range into myrange and then activate the range on the worksheet...
Here is the current coding attempt (which leaves the cursor at the top of the column and an
"TypeError: Cannot find function getRangeByName in object Sheet. (line 170, file "macros")"
//Get EOD range, select, index up 3 rows to start row insertions
function getEOD() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
Logger.log(ss); //lOG is not helpful, says, "sheet", not SheetName
var MyRange = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRangeByName("EndData");
Logger.log(MyRange); //lOG is not helpful, says, "Range", not RangeAddress
//Activate the named cell, moves with the spreadsheet
MyRange.activate();
};
Had a new idea after I asked for help, here's the working code that gets the job done:
//Get EOD range, select, index up 3 rows to start row insertions
function getEOD() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//Logger.log(ss); //lOG is not helpful, says, "sheet", not SheetName
var rg = SpreadsheetApp.getActiveSpreadsheet().getRangeByName('EndData');
if (rg != null) {
Logger.log(rg.getNumColumns());
}
//Logger.log(rg); //lOG is not helpful, says, "Range", not RangeAddress
//Referenced the Named the EOD cell
//Future, Trying to create a debug status bar
//SpreadsheetApp.getActiveSpreadsheet().toast("looking at row" & i "now")
//Activate the named cell, which moves with spreadsheet growth, down
rg.activate();
//Uncomment for testing purposes, places a Yes on the row 4 columns over
//sheet.setActiveRange(sheet.getRange(0, 4)).setValue('Yes');
//turned off durning testing, writes in data range with this trial code
//Reposition from named cell to insert lines location
ss.getRange(sheet.getCurrentCell().getRow() -2, 1, 1,
sheet.getMaxColumns()).activate();
//Insert ten lines, copy and paste special formulas only
Insert_10_Lines()
//https://stackoverflow.com/questions/59772934/get-range-from-
getrangebyname-activate-range
};
My answer is I have tenacity, I try to limit the variables one at a time to prove how things really work, moved the code that did work in the test script file to a production script file and it didn't work for copy of what I had in test, went back to the test script and it didn't work either, after it had... There are variables in play that seem to be happening at the scripting, savings and running steps that change the interactive responses I am getting. Trial and error again through the production environment came up with a working combination by going to the simples code suggested and getting a combination that works. Here is the the code that is running in product to the main need of my question...
function InsertRows() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//Get range from named cell
var range = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("EOD");
if (range != null) {
Logger.log(range.getNumColumns());
}
//Select the cell as a starting point for code to follow
range.activate(); //start location cell is active and row is selected
Thanks for those that responded for the help! Looks like it is going to take a while to recognize the patterns and figure out what to do to get consistent results in a timely manner...
Try this:
function getEOD() {
var ss=SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var MyRange=ss.getRangeByName("EndData");
MyRange.activate();
}

Google Script Function - Copy Paste

I was writing a script through Google Script about the function of a button when clicked. What I want to happen is SHEET 1 Values gets copied to SHEET 2 AS VALUES (Not copying the Google Sheets Formulas), then SHEET 1 VALUES will get cleared. However, it seems I'm having an issue with the values getting copied to SHEET 2.
I tried to search for something that could resolve this, but I'm not really that an expert when it comes to writing scripts since I'm a newbie to this.
// Display a dialog box with a message and "Yes" and "No" buttons.
var ui = SpreadsheetApp.getUi();
var response = ui.alert("Do you want to capture all data?", ui.ButtonSet.YES_NO);
// Process the user's response.
if (response == ui.Button.YES) {
}
function remove() {
var spreadsheet = SpreadsheetApp.getActive().getSheetByName("2019")
var destsheet = SpreadsheetApp.getActive().getSheetByName("Handled Tickets");
var getLastContentRow = spreadsheet.getRange("A8:I").getValues();
var destination = destsheet.getRange(destsheet.getLastRow()+1,1);
var source = spreadsheet.getRange("A8:I").getValues();
getLastContentRow.copyTo(destination.CopyPastType.PASTE_VALUES);
spreadsheet.getRange('C8:E').clearContent()
spreadsheet.getRange('F8:H').clearContent()
}
Expected Flow: 1) When the button has been clicked, whatever data in spreadsheet will be copied to destsheet. 2) Once copied, data in spreadsheet will be cleared.
Additional rules: 1) Once copied to destsheet, data will not be overwritten by other values when the button is clicked again. Instead, it will look for the last row (empty cell) and copy the data there. 2) If all cells have been used, automatically there will be additional 100 rows added.
Error:
Cannot find function copyTo in object
There are several issues with your code above (syntax, format, structure, missing semicolons to finish statements,...).
Assuming only the remove() function was being a problem, here is my version below with several comments.
You may also want to review the part with the UI above (e.g. embed it in a function that your button will call, make sure there is some code in your if statement,...).
function remove() {
var source_sheet = SpreadsheetApp.getActive().getSheetByName("2019"); // better not use "spreadsheet" as variable name here, this is confusing, your content is a sheet
var dest_sheet = SpreadsheetApp.getActive().getSheetByName("Handled Tickets");
var getLastContentRow = source_sheet.getRange("A8:I"); // The "copyTo" method applies to ranges, not to arrays, so remove the ".getValues()"
// --> the "getLastRow" variable name makes me believe you're only looking at copying the last row, but your current range will copy all rows starting at 8.
// --> as the same content is captured in "source" below, this might just be a misleading variable name, though, in which case you may want to simply rename it
var destination = dest_sheet.getRange(dest_sheet.getLastRow()+1,1);
// var source = spreadsheet.getRange("A8:I").getValues();
// --> this is duplicate with getLastContentRow, and not use in your function, so presumed useless. Can be removed.
getLastContentRow.copyTo(destination, SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
// --> the example in the documentation is misleading, but this function requires a third argument for "transposed"
// spreadsheet.getRange('C8:E').clearContent()
// spreadsheet.getRange('F8:H').clearContent()
// --> why two different calls instead of 1 on C8:H directly?
// --> also, why not the same range as the one copied?
getLastContentRow.clearContent(); // This will remove all the copied content from the "source_sheet"
}