Trying to copy a variable-length list - google-apps-script

I'm trying to copy a list from Sheet A to sheet B. (I have the number of rows used by the data in the list saved in a cell, if needed.)
The spreadsheet file contains 5 sheets, of which "Analysis" is one. It contains 4-5 tables, each separated by a blank row.
The list itself is variable in length - sometimes it's 9 rows, sometimes its 17. Thankfully, it's always 5 columns wide.
Once I get the list into Sheet B, I need to set all cell borders to TRUE.
Here's the code that I've been playing with:
var sss =
SpreadsheetApp.openById(Sheet A);
var ss = sss.getSheetByName('Analysis'); //
var tblRows = sss.getRange('Analysis!K190').getValue();
var range = SpreadsheetApp.getRange('190,5,tblRows,5'); //assign the range you want to copy
var data = range.getValues();
var tss =
SpreadsheetApp.openById(Sheet B);
var ts = tss.getSheetByName('2018');
ts.getRange(ts.getLastRow()+1,1,tblrows,5).setValues(data);
(I'm not sure that this formatted properly)
Anyhow, I have the number of rows used by the list saved as a variable in a spreadsheet cell, and I'd like to use that, because I have found "getLastRow" to get the last row of the sheet, not the last row that is non-blank. (It might be giving me trouble because I'm doing something wrong, tho...) I know that I've messed up, so feel free to throw everything out and start over, if needed.
Anybody care to throw some ideas my way?

Related

Copy data range and paste to new page, and repeat

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

Loop script to copy and paste data, how to optimise

I got a sheet with data automatically refreshed everyday. Here is the structure:
data1 data2 data3 data4 data5 etc...
a1 a2 a3 a4 a5 etc...
b1 b2 b3 b4 b5 etc...
c1 c2 c3 c4 c5 etc...
I wrote a script to save these data in another sheet everyday. here is my script :
function savedata() {
var sheet = SpreadsheetApp.openById('my_id').getSheetByName('Sheet1');
var numRows = sheet.getLastRow()-1;
for(var i = 0; i < numRows; i++) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2")
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1")
var data1 = sheet.getRange('Sheet1!A2:G2').getValues();
sheet.appendRow(data1[0]);
ss.deleteRow(2)
}
}
As you can understand I'm looping through all my row copying them and deleting them one by one until there nothing left. Everything is added to a second sheet.
Is there a more effective way to do this job ?
Indeed we can optimize this code.
Few points to keep in mind:
Always try to use inbuilt appscript functions first instead of looping as looping consumes more time than inbuilt functions on server side and ultimately your script will be slow when data becomes huge.
Also, in your for loop, you have written SpreadsheetApp.getActiveSpreadsheet(). It will get the spreadsheet every time the loop runs, you dont have to do that, declare them outside of the loop and still you will get the same result. Unless your data is getting modified runtime, you dont want to do that.
Also, I don't think your data1 variable is fetching new range for new iteration of for loop, I think it brings the same row values and you will write same data in multiple rows in your destination sheet.
Now, you can code something like this to get the similar result in efficient way:
var sheet1 = SpreadsheetApp.openById("ID1").getSheetByName("Sheet1");
var sheet2 = SpreadsheetApp.openById("ID2").getSheetByName("Sheet2");
var content = sheet1.getRange(1, 1, sheet1.getLastRow(), sheet1.getLastColumn()).getValues(); //Get all the values starting from 1st row 1st column and till end of rows and columns
sheet2.getRange(1, 1,sheet1.getLastRow(), sheet1.getLastColumn()).setValues(content); //Get the same length and width to paste the data
sheet1.clear(); //You can put a condition here to make sure that all the data is copied successfully by checking if no. of rows in sheet1 and sheet2 is same
Also, there is difference between sheet1.clear() and sheet1.deleteRow(). If you use clear, it will just make the spreadsheet cells blank. If you use delete, it will delete the rows entirely and if you have only 5 rows, then after deletion, it will show you warning that you can't delete all the cells.
One more thing, delete fully blank rows and columns if you have no use of those as there is limit of 20 million cells in spreadsheets which includes cells in all the tabs.
EDIT
if(sheet2.getRange(1, 1).getValue().trim() == "")
{
var startIndex = 0; //I'm assuming your destination sheet is blank in first iteration
}
else
var startIndex = sheet2.getLastRow();
sheet2.getRange(startIndex+1, 1,sheet1.getLastRow(), sheet1.getLastColumn()).setValues(content);
Also one more important thing which I just added, see that I used .trim() in the if statement. The reason is, lets say you have entered 4-5 blank spaces by pressing spacebar in the A1 cell of destination sheet, then it will be considered as written row and getLastRow() will return 1 instead of any error. So, trim() here will remove all the white spaces of the A1 cell and check if it is still blank. It is very useful when you're checking for blank cells or values so that no once can bypass the code.

using query() with apps script keeps adding 500 rows to sheet

I'm working on a large sheet and cells are precious given gsheets quota.
I have a range, and when data updates automatically a script updates the named range automatically to be the full length of the data.
The named range is called "gadatapull". This range is on the tab "datapull".
Two tabs. "datapull" is where fresh data is dumped and "data_prep" is where I do stuff to the data. After a fresh pull just now datapull has 2,733 rows of data, including the headers.
I would like data_prep to have the same length as datapull. Plus 7 rows for text at top of data_prep.
When my script to update data runs I do this:
// clear dataprep sheet for new data
var lastRow = 7;
var maxRows = dataprep.getLastRow();
if(maxRows - lastRow > 0) {
dataprep.deleteRows(lastRow+1, maxRows-lastRow);
}
data_prep has 7 rows (because the script just deleted all rows above 7).
Now, in data_prep cell A7 I have:
=query(indirect("gadatapull"),"select *")
Expected result was that all the fresh data in "gadatapull" would appear in data_prep tab and that data_prep tab would expand accordingly.
But what actually happens is all the data arrive as expected, but then there are an additional blank 500 rows at the bottom. This 500 number is too rounded off. Makes me think Gsheets is automatically adding this number as a default under some condition.
How can I prevent Gsheets from adding these additional 500 rows?
Instead of letting the sheet API expanding the number of rows (which is your hypothesis and might well be true ;-) you can add all the necessary cells before importing data.
I didn't try in real conditions but this should work.
Btw, the script imports data as well.
code :
function copyDataToSheet(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataprep = ss.getSheetByName('data_prep');
var datapull = ss.getSheetByName('datapull');
var lastRow = 7;
var maxRows = dataprep.getLastRow();
if(maxRows - lastRow > 0) {
dataprep.deleteRows(lastRow+1, maxRows-lastRow);
}
var datapullSize = datapull.getLastRow();
dataprep.insertRows(7,datapullSize);// insert exactly the number of rows you need.
var dataToCopy = datapull.getDataRange().getValues()
dataprep.getRange(7,1,dataToCopy.length,dataToCopy[0].length).setValues(dataToCopy);
}

Issue applying script to second sheet

I have no idea how to code. I use to make website on HTML, so my knowledge is limited. I piece together and alter existing codes. I got decent on Excel VBA, but then needed to start using google sheets. So, that said...
I have a spreadsheet with two sheets, 'MIS' and 'Admin'. I have a bunch of code a formula that assigns a value (1,2,3...) based on how many of two drop-down criteria that each line matches (1 if it matches criteria #1, 2 if matches criteria #2, and 3 if it matches both). The code then hides everything and unhides only those with numbers matching the criteria. I need this to be clean and quick, it's for people who can barely use computers.
The problem is, the code only works on the first page. I tried using the same code, tried amending the code, and I tried inserting 'Admin' in about half a million places. Please help. The admin function is my latest attempt. This is where I inserted 'Admin' in a dozen places. Also, if you see anything I'm using that is slowing down the code, I could use some help with that too. There are 6 functions, which basically do the same thing using the same code but corresponding to different number combinations. The one in question is below.
function Admin(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets('Admin')[0];
var range = sheet.getRange(1, 1, sheet.getLastRow());
sheet.hideRows(8,sheet.getLastRow());
var values = range.getValues();
for (var i=0; i<values.length; i++){
if(values[i][0] === 3){
sheet.showRows(i+1);
}
}
}
This line is your 'only the first sheet' problem, you're sort of combining two methods of defining a sheet:
var sheet = ss.getSheets('Admin')[0];
You can get a specific sheet by name by using the .getSheetByName() method i.e.
var adm_sheet = ss.getSheetByName('Admin');
var mis_sheet = ss.getSheetByName('MIS');
Or you can get a sheet by index
var adm_sheet = ss.getSheets()[0]; //returns first sheet
var mis_sheet = ss.getSheets()[1]; //returns second sheet
Lastly, you can get all of the sheets in your spreadsheet and list them in an array like this:
var sheets = ss.getSheets();
And then use the index of the sheet you need in this array like so:
sheets[0] //the first sheet
sheets[1] //the second sheet

How to copy row ranges in Google docs?

I have a Spreadsheet, like excel on Google Docs. I am using both Mozilla Firefox and Google Chrome, whichever works. Almost all my columns have dropdown list validation(you know, each cell has a dropdown list to select, I hope I made it clear). I arranged them when I first created the spreadsheet, gave all the columns validation from ranges I created.
My problem is, whenever I add a new row, that row doesn't have any validations, all of them are gone. The old rows still have the validations.
So then, I set the validations every time I add a new row, one by one. This is frustrating. Some people also had the same problem, asked online, but no one answered.
When I copy an empty row with validations and paste it on the new row, it works fine. So, what I am saying is, can you help me write a script for it? Like copying 5 rows when I execute the script?
I am trying to study the scripts but I did nothing nothing so far. I think
var actSc = SpreadsheetApp.getActiveSpreadsheet();
var range = actSc.getRange("A1:B1");
This all I got from the examples I saw. I mean it. I got nothing.
If this copies the ranges of one cell, then I guess I should do it for all my columns.
But how do I put them in the new row? Is there something like setRange?
I could really use some help. This is driving me crazy and I really don't get this script thing.
What I mean by range is that I have ranges like "STATES" and it includes "NY,LA,CA" etc. This NY,LA,CA fills the dropdown list in the cells of that STATES column. I hope this getRange means this range.
Sorry about my English.
If I understand correctly, you want to script a function that will add new rows to a sheet and maintain the existing validations for your columns. This is certainly possible and not too difficult. One approach could be a "refresh validations" function that updates your entire sheet all at once, in the event that you want to reuse it in other sheets. First, though, it sounds like you could use a brief overview of the object classes you need to know about to do basic Google Apps Scripts:
SpreadsheetApp - Think of this class as the foundation of the Spreadsheet Service. It provides file I/O and functionality that is not tied to specific spreadsheets, per se, such as UI and the creation of Data Validation sets. It's the interface to all of your individual spreadsheet documents.
Spreadsheet - A spreadsheet document file, which can contain multiple Sheets. This is what gets created when you create a new Google Sheets document in Drive. Provides document-level functions, such as the ability to manage ownership, set permissions, access metadata, etc. There's some overlap with the Sheet class, so this one can seem like a bit of a mishmash.
Sheet - An individual sheet is what you normally think of as a spreadsheet: a set of rows and columns. Each Spreadsheet document can contain many, distinct Sheets. The Sheet class lets you modify the overall appearance of the sheet. You can freeze or hide rows, protect ranges of cells from being edited, add/delete rows and columns, etc. You can also get data about the sheet, such as the last row that has content or the maximum range of the whole sheet.
Range - Dropping down another level, we reach the Range object, which represents a certain rectangular area of cells. This can be as small as a single cell or as large as the whole sheet. It does not seem possible, however, for Ranges to represent discontiguous cells. This is where you had some trouble, because you treated the Range object as content that you could copy and paste in your sheet, which is understandable. But a Range isn't the data in the cells it represents. It's just an interface to those cells. If you want to access the data itself, you have to drop down to the bottom level of the hierarchy:
Value - The actual contents of your sheets are normal JavaScript values: strings, integers, Booleans, etc. that you can manipulate with the subset of JavaScript that Google Apps Script supports.
In order to do something with the values in your sheet, you first get the Range object from the Sheet (which you get from the SpreadsheetApp) and then get the values from the Range:
var values = SpreadsheetApp.getActiveSheet().getRange("A1:B1").getValues(); // returns [[]]
Note that getValues() returns a multi-dimensional array. As a representation of the values in your sheet, it looks like this:
// row 1 [[column A, column B, column C, column D, ...],
// row 2 [column A, column B, column C, column D, ...],
// row 3 [column A, column B, column C, column D, ...],
// row 4 [column A, column B, column C, column D, ...],
// row 5 [column A, column B, column C, column D, ...], ...]
So if the range A1:B1 is a range of one row and two columns, you can retrieve the values with A1 notation or by specifying the upper left row and column of the range, and the number of rows and number of columns you want to retrieve:
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("A1:B1");
var range = sheet.getRange(1, 1, 1, 2); // row 1, column 1, 1 row, 2 columns
var values = range.getValues(); // returns [[50, 100]]
If the value in A1 is 50, and the value in B1 is 100, the last function above will return [[50, 100]]. You can access individual cell values directly, too:
var range = sheet.getRange("A1");
var value = range.getValue(); // returns 50
var cell = range.getCell().getValues(); // returns [[50]]
Obviously, you can set the values of ranges, too:
var range = sheet.getRange("A1:B2");
range.setValues([[50, 100]]);
range = sheet.getRange(1, 1); // same as sheet.getCell(1, 1)
range.setValue(50); // the value of A1, or row 1 column 1, is now 50
The next step is to figure out how the Data Validation class works. You create a Data Validation object using the Data Validation Builder, which lets you chain together a series of rules to apply to a range. You then set the range to that Data Validation rule set:
var stateList = ["AK", "AL", "AR", ...];
var rules = SpreadsheetApp.newDataValidation() // create a new Data Validation Builder object and use method chaining to add rules to it
.requireValueInList(stateList, true) // first param is the list of values to require, second is true if you want to display a drop down menu, false otherwise
.setAllowInvalid(false) // true if other values are allowed, false otherwise
.setHelpText("Enter a state") // help text when user hovers over the cell
.build();
range.setDataValidation(rules); // apply the rules to a range
Now you can insert rows and the rules should copy over into them automatically:
var lastRow = sheet.getLastRow(); // get the last row that contains any content
sheet.insertRowAfter(lastRow);
Or copy the rules and use them elsewhere:
var cell = sheet.getRange(1, 1, 1, 1);
var rule = sheet.getDataValidation(); // returns rule
var range = sheet.getRange("A1:B1");
var rules = range.getDataValidations(); // returns [[rules, rules]]
var lastRow = sheet.getLastRow(); // or sheet.getMaxRows()
range.setDataValidations(rules);
So you can very easily put these concepts together to write whatever sort of function you need to add rows, build validation rule sets, and add validations to new ranges of cells. You can do most of these things more concisely than I have here, but it sounds like you're looking for a more in-depth explanation. I hope it helps.
var sheetToUpdate = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheetToUpdate.insertRowAfter(sheetToUpdate.getLastRow());
var rangeToUpdate = sheetToUpdate.getRange(sheetToUpdate.getLastRow()+1,1,1,sheetToUpdate.getMaxColumns());
sheetToUpdate.getRange(sheetToUpdate.getLastRow(),1,1,sheetToUpdate.getMaxColumns()).copyTo(rangeToUpdate, {formatOnly:true});