Copy data range and paste to new page, and repeat - google-apps-script

I am trying to copy a range from sheet 'Full' and paste the values only to a new sheet, 'Dump'. While the macro below does its action once, I am regenerating the original data range (Full), so I want to copy that new set and append to the same output page, indexed down to a blank row and keeping the first pasted data. Also then to do this 100 times.
The recoded macro is below, and I need to understand the script to add in to;
repeat the copy/paste function 100 times, and also
offset the paste range by a set number of rows.
Sorry, genuine newbie at editing google sheet macros. The Excel macro I use doesn't translate over.
Appreciate any answers you have.
function xmacro() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A1').activate();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Full'), true);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Dump'), true);
spreadsheet.getRange('Full!BK3:BT34').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);```
};

Your macro is just an automatically generated app script. You can extend its functionality by building off that with some more code. First I'll explain some of the basic concepts, if you know this, then just skip down to the code.
Sheets Concepts
Here are some basic concepts that took me forever to figure out because most of the documentation assumes you are already proficient at Javascript.
A range is a 2 dimensional array that has one array for each row, and the contents of that array are the columns:
someRange = [
[row1Col1, row1Col2, row1Col3, row1Col4],
[row2Col1, row2Col2, row2Col3, row2Col4],
[row3Col1, row3Col2, row3Col3, row3Col4]
]
To access a specific value you need to reference the row array, and then the index of the column you want.
Think about it like hotel room numbers. The first part of the number is the floor,
and the second part is the specific room on that floor.
You access arrays by calling the array name, then square brackets with the index number of the element you want.
Arrays are indexed starting at 0, so to get row 1 you would use:
someRange[0] would return the inner array [row1Col1, row1Col2, row1Col3].
But that doesn't give you a specific cell values - so you would use a second set of brackets to access the column in that row:
someRange[0][1] = 'row1Col2'
Arrays also have built in information, so you can find the length of an array by using Array.length no parenthesis.
Since the rows are in the outer array, you can get the number of rows by seeing how many inner arrays there are.
someRange.length = 3 There are 3 row arrays in the someRange array.
You can do the same with columns, since the number of columns is equal to the number of elements in an array. To get the number of elements in the first row you would use:
someRange[0].length - which would be 4
And since a range has the same number of columns for each row, you can pick any row
to get the number of columns (generally, there are always exceptions)
The Code
The first function will create a custom menu item to run the code.
// create a new menu item for your custom function
function onOpen(){
SpreadsheetApp.getUi().createMenu()
.addItem('100 Copies', 'lotsOfCopies')
.addToUi();
}
function lotsOfCopies() {
var ss = SpreadsheetApp.getActive();
var copySheet = ss.getSheetByName('yourCopySheetName');
var pasteSheet = ss.getSheetByName('yourPasteSheetName');
// the range you wish to copy, change to fit your needs
var copyRange = copySheet.getRange('A1:B7');
var copyValues = copyRange.getValues();
var copyRows = copyValues.length;
var copyCols = copyValues[0].length;
// define the first row to be pasted into
var pasteRow = 1;
// define the left side column of the range to be pasted into
var pasteCol = 1
// build a loop that does the same thing 100 times,
// and each time offsets the paste range by the number of rows in the copy range
for (var i = 0; i < 100; i++) {
// for every iteration after the first,
// add the number of rows in the copy range to the variable 'row'
// example if there are 10 rows in the copy range then
// iteration 1 row = 1 Iterartion 2 row = 11, Iteration 3 row = 21
if (i > 0) {
pasteRow = +pasteRow + +copyRows
}
// build the range to paste into - it starts on pasteRow and paste col,
// and is as many rows as the copied range, and as many columns as the copied range
let pasteRange = pasteSheet.getRange(pasteRow, pasteCol, copyRows, copyCols);
// put the values from copyValues into the pasteRange
pasteRange.setValues(copyValues);
}
}

function xmacro() {
const ss = SpreadsheetApp.getActive();
const ssh = ss.getSheetByName('Full')
const dsh = ss.getSheetByName('Dump')
ssh.getRange('BK3:BT34').copyTo(dsh.getRange('A1'), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}

Related

how to make sure that a variable always refers to the same range (not the same address) in a google sheet

I want to assign a range to a variable and later set some of its cells even if in the meantime its address has been changed due to some row/column insertion/deletion.
In the following snippet the strings are written to the same row even if I need to write to two different rows, the one created first and the one created second.
function addingRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var r1=sheet.getRange("1:1")
sheet.insertRowBefore(1);
// now r1 continue to refer to the first row even if it has became the second one!
var r2=sheet.getRange("1:1")
// I want to write on the second row (the first row that shifted one row below due to the row insertion)
// but the following line writes to the first row!
r1.getCell(1,1).setValue("first created row")
// the following line writes to the first row as expected
r2.getCell(1,2).setValue("second created row")
}
In other words, I want to write "first created row" in the previously-first row that became the second one after the row insertion, but it seems that variable r1 points to the first row even after the row insertion.
How to do it?
function addingRows() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const r1 = sh.getRange("1:1")
let n = 0;
sh.insertRowBefore(1);
n++;
const r2 = sh.getRange("1:1")
r1.getCell(n + 1, 1).setValue("first created row")
}

Find last row with Data in Column D starting the search at Row 4 in Google App Script

I'm trying to find a way to find the last row with data in Column D. I also want the search to start at Row 4.
I'm really struggling and would appericate any help please.
You can refer to this code:
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var rowOffset = 3;
var count = sheet.getRange('D4:D').getDisplayValues().flat().filter(String).length;
var lastRow = count+rowOffset;
Logger.log(lastRow);
What it does?
Select Range D4:D and get its value, since you want to get the last row with data in column D starting at row 4.
Use array.flat() to change 2-d array into 1-d array.
Use array.filter() to remove empty values. Then get it's array length
To get the last row index, Use the cell count that has data which we obtained in step 3 and add it to 3 (start offset since we start our search at row 4)
Note:
This solution will only work assuming you don't have empty rows in between.
Output:
Execution log
2:01:53 AM Notice Execution started
2:01:54 AM Info 13.0
2:01:55 AM Notice Execution completed
There are many ways to find the last value in a column, but here is one. See if this helps!
function myFunction() {
const spreadsheet = SpreadsheetApp.openByUrl('Insert Sheet URL Here');
const sheet = spreadsheet.getSheetByName('Insert Sheet Name Here - e.g. Sheet1');
const lastSheetRow = sheet.getLastRow();
let lastColumnRow = 'This column is empty';
let reversedColumnValues = sheet.getRange(1, 4, lastSheetRow).getValues().reverse();
for (index in reversedColumnValues) {
if (reversedColumnValues[index][0] != '') {
lastColumnRow = lastSheetRow - index;
break;
}
}
Logger.log(lastColumnRow);
}
This Apps Script Video may help you out too: https://www.youtube.com/watch?v=1Po1QElOFPk

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.

How could a dummy Browser.msgBox call affect output in GAS?

I have a script that moves data from one sheet to another and as part of this must insert the appropriate number of rows into the second sheet to account for the data from the first sheet. I'm running into an issue where, for reasons I don't quite understand, the data is duplicated in the second sheet, e.g. if 94 rows are in the original sheet, the second contains these 94 rows then an additional 94 rows right below with the exact same data. My code is:
function moveToEntrySheet(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var rawRow = 2;
var entrySheet = ss.getSheetByName('Sheet2');
var rawSheet = ss.getSheetByName('Sheet1');
var entryRow = 4;
var numEntries = rawSheet.getLastRow() - 1;
if (numEntries <= 0){
return;
}
Browser.msgBox(numEntries);
entrySheet.insertRowsBefore(entryRow, numEntries);
Browser.msgBox(numEntries);
var entryRange = entrySheet.getRange(4, 2, numEntries, 6);
var vals = rawSheet.getRange(2, 1, numEntries, 6).getValues();
Browser.msgBox(numEntries);
entryRange.setValues(vals);
}
To try and debug this I put calls to Browser.msgBox() in my code to make sure that numEntries is the expected number (94), which it does appear to be at each of the 3 points that I put the call in. The weird thing is that when I run this function with the calls to msgBox() included, the entries are only pulled to the other sheet once. If I keep the first call in it works the same, and if I keep the second or third call in double the number of rows that should be inserted are inserted but the data isn't copied itself, essentially giving n empty rows in the sheet, where n is the number of rows of data in sheet 1. How could calls to Browser.msgBox() affect the copy in this way, and how can I avoid the copy from duplicating? Thanks!

Autofill google forms based on user input

Alright stack friends,
I'm working on my first projects using google scripts and it's been pretty fun so far. My project is to create a form for data entry that can either accept an ID number and fill in the rest of the fields, or let the user fill out the entire form. Basically my method to fill in the other fields is just to have a lookup table on the second sheet. When the user submits a form, the script runs, looks for the ID of the last row, scans the reference table for the ID, and then fills in the details.
I think the problem I'm having is the assumption that the data from the form is already in the sheet when the script runs. The problem I noticed is that the script sometimes fails to fill in the gaps. I tried creating form submissions in a loop with the same ID and they function somewhat erratically but it seems like the last sumbission always works which would make sense if the script executions are not matching up with the form submissions. Here's the script for reference:
function fillGaps() {
// First take in the appropriate spreadsheet objects and get the sheets from it
var ss = SpreadsheetApp.openById(id);
var sheet = ss.getSheets()[0];
var refSheet = ss.getSheets()[1];
// Here's the last rows' index
var lastRow = sheet.getLastRow();
var lastRowRef = refSheet.getLastRow();
// now this is an array of values for the last row and the student ID entered
var response = sheet.getRange(lastRow, 1, 1, 7).getValues();
var enteredID = response[0][1];
// Next we're going to try to load up the lookup table and scan for the ID
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()).getValues();
var row = 0;
while(enteredID != stuIDs[row] && row <= lastRowRef){
row++;
}
// Okay at this point the row variable is actually -2 from what the sheet index
// is that I'm thinking of. This is because we didn't load the first row (names)
// and the way arrays are indexed starts with 0.
row++;
row++;
// now assuming that it found a match we'll fill in the values
if(row < refSheet.getLastRow()){
// Alright now we need to wrangle that row and format the data
var matchedRow = refSheet.getRange(row, 1, 1, 6).getValues();
// modify the response
var replacement = [response[0][0],enteredID, matchedRow[0][1],matchedRow[0][0],matchedRow[0][2],matchedRow[0][4],matchedRow[0][5]];
sheet.getRange(lastRow, 1, 1, 7).setValues([replacement]) ;
}
}
So I'm wondering:
Does this seem like the right diagnosis?
If so, what would be the best way to remedy? I thought of adding a little delay into the script as well as trying to capture the submissions timestamp (not sure how to do that)
Thank you much!
The following code gives a 2D array:
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()).getValues();
Also,refSheet.getLastRow gives the last row, lets say it is 10 in this case. The syntax for getRange is getRange(row, column, numRows) and the last argument is the number of rows, not the last column. So in the above code the selected range would be row 2 - 11 rather than 2- 10. Unless that is what you intended, modify the code like so:
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()-1).getValues();
To access the values in stuIDs you should use stuIDs[row][0] (2D array) to check for matching ID. Assuming your ID was to be matched was in column 1.
Secondly, in the loop you are using the following to check for the last index in array row <= lastRowRef which will cause it go out of range(because array starts at 0 and sheet row at 1) instead use this row < stuIDs.length
Finally, in case you don't find a match you will end up with the last row and your code will end you taking the last row as the matched index. This can be prevented by using a boolean variable to check for a match.
var foundId = false
var row = 0;
var i = 0;
for (i in stuIDs){
if(stuIDs[i][0] == enteredID)
foundID = true
break
}
}
row = i + 2
if (foundID){
var matchedRow = refSheet.getRange(row, 1, 1, 6).getValues();
// modify the response
var replacement = [response[0][0],enteredID, matchedRow[0][1],matchedRow[0][0],matchedRow[0][2],matchedRow[0][4],matchedRow[0][5]];
sheet.getRange(lastRow, 1, 1, 7).setValues([replacement]) ;
}
PS: You can also use event objects to get the values of response (eventObj.values). As mentioned here: https://developers.google.com/apps-script/guides/triggers/events
Hope that helps!