Google script - Why copyTo in for-loop doesn't work? - javascript

I'm trying to do copy-paste (only values) in some dynamic parts of my spreadsheet in order to get it ready to be exported to excel. I'm trying to use the copyTo method using an array and a nested for-loop.
// here i define all the sheets
var activeSS, mikumimSheet,rankSheet, conveSheet, srcSheet, clkImpSheet, gaSheet, salesSheet, lndpageSheet;
activeSS = SpreadsheetApp.getActiveSpreadsheet();
mikumimSheet = activeSS.getSheetByName('עותק של מיקומים בגוגל');
rankSheet = activeSS.getSheetByName("מיקומים בגוגל");
conveSheet = activeSS.getSheetByName("המרות");
srcSheet = activeSS.getSheetByName("מקורות תנועה");
clkImpSheet = activeSS.getSheetByName("חשיפות והקלקות");
gaSheet = activeSS.getSheetByName("Google Ads");
salesSheet = activeSS.getSheetByName("מכירות באתר");
lndpageSheet = activeSS.getSheetByName("דפי נחיתה");
// here some code inject data to cells all over the spread sheet
// setHeaders();
// here i store all the cells i need to copy paste and run the copyTo method
function copyPasteReplace() {
var cells = [
[srcSheet, "A2:G2", "B53:C53"],
[salesSheet, "A1:F1"],
[clkImpSheet, "A1:E1"],
[rankSheet, "A1:E1", "C11:E11"],
[conveSheet, "A1:G1", "A30:H30", "O11:O17"],
[lndpageSheet, "A1:D1"],
[gaSheet, "A1:F1"]
];
for (var i = 0; i < cells.length; i++) {
for (var n = 1; n < cells[i].length; n++) {
cells[i][0].getRange(cells[i][n]).copyTo(cells[i][0].getRange(cells[i][n]), {
contentsOnly: true
});
}
}
}
// this is the order of execution
function onOpennn() {
setHeaders();
copyPasteReplace();
}
I expected all the values in the sheets first get injected and then pasted in value form (without formulas).
What actualy happens in that the cells just get emptied.

I found the problem with my code. It turns out, that i had to add the sheet name in the ranges array, like so:
// instead of:
var cells = [
[srcSheet, "A2:G2", "B53:C53"],
[salesSheet, "A1:F1"],
[clkImpSheet, "A1:E1"],
[rankSheet, "A1:E1", "C11:E11"],
[conveSheet, "A1:G1", "A30:H30", "O11:O17"],
[lndpageSheet, "A1:D1"],
[gaSheet, "A1:F1"]
];
// It should have been:
var cells = [
[srcSheet, 'מקורות תנועה!A2:G2', 'מקורות תנועה!B53:C53'],
[salesSheet, 'מכירות באתר!A1:F1'],
[clkImpSheet, 'חשיפות והקלקות!A1:E1'],
[rankSheet, 'מיקומים בגוגל!A1:E1', 'מיקומים בגוגל!C9:H9'],
[conveSheet, 'המרות!A1:G1', 'המרות!B24:G24', 'המרות!O11:O17'],
[lndpageSheet, 'דפי נחיתה!A1:D1'],
[gaSheet, 'Google Ads!A1:F1']
];
I don't know the reason for it, because the sheet is already defined,
but that what fixed it.

Related

Automatically generate new divisions and label it

I'm trying to use a script that automatically creates divisions on a spreadsheet. It receives as a value the number of times it has to create the same category of division. Each division/label it's composed of a merge of 6 cells in the same line.
I'm trying to make it work by using getLastRow as a base of the placement of the label, but I can't make it work it out with the merge.
Basically what I'm doing is:
function resumo() {
let ss = SpreadsheetApp.getActive();
let resumo = ss.getSheetByName("Resumo");
let numEntrada = resumo.getRange("c12").getValue();
criaParcela();
function criaParcela() {
for (i = 0;i < numEntrada; i++){
var fLine = resumo.getLastRow();
var bcell = (fLine+1);
var fcell = (fLine+6);
resumo.getRange(fcell,1).setValue("Entrada");
resumo.getRange(bcell,1,6,1).mergeVertically();
}
}
}
As you can notice, I'm not professional programmer.
Assuming that this is the end result you are looking for:
You can try the below script:
function doStuff() {
let spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
let sheetResumo = spreadsheet.getSheetByName("Resumo");
let numEntrada = sheetResumo.getRange("C12").getValue();
for (let i = 0; i < numEntrada; i++) {
let lastRow = sheetResumo.getLastRow() + 1;
sheetResumo.getRange(lastRow, i+1, 6, 1).mergeVertically();
}
}
The most notable changes that have been done to your script are the following:
+ using only one function;
Apps Script does not accept a function within a function in the way you had tried to use it.
+ getRange method is getting the proper parameters in order to perform the merge operation successfully;
As you can see, the i index is needed if you want to merge the same cells numEntrada multiple times.
Reference
Apps Script Sheet Class - getRange(row, column, numRows, numColumns).

Google Apps Script: Loop through a list

I have a deleteEachRow function that loops through a sheet and delete Rows that have a particular Column Value.
This works fine and was hoping to modify it in such a way that it loops through a multile sheets in the work-book and also delete rows based on multiple Column Values.
The deleteRow() script
//GLOBALS
var SS = SpreadsheetApp.openById("sheetID");
var SHEET = SS.getSheetByName("Sheet1");
var RANGE = SHEET.getDataRange();
var DELETE_VAL = "abc";
var COL_TO_SEARCH = 4; // The column to search for the DELETE_VAL (Zero is first)
function deleteEachRow(){
var rangeVals = RANGE.getValues();
//Reverse the 'for' loop.
for(var i = rangeVals.length-1; i >= 0; i--){
if(rangeVals[i][COL_TO_SEARCH] === DELETE_VAL){
SHEET.deleteRow(i+1);
};
};
};
What I have tried..
var SHEET = SS.getSheetByName(["Sheet1", "Sheet2"]);
var DELETE_VAL = ["abc","DEF"];
function deleteEachRow(){
var rangeVals = RANGE.getValues();
//Reverse the 'for' loop.
for(var i = rangeVals.length-1; i >= 0; i--){
for(var i=0; size = DELETE_VAL.length; i < size; i++){
if(rangeVals[i][COL_TO_SEARCH] === DELETE_VAL[i]){
for(var i=0; size = SHEET.length; i < size; i++){
SHEET[i].deleteRow(i+1);
};
};
};
};
};
Which completes executing from my logs, but does not actually work. I may have murdered some logic here, please pardon me, I am new to .gs/.js.
Thanks for your anticipated response.
Issue : You're passing array to getSheetByName, whereas as per documentation it accepts String only. i.e. Name of the single sheet you want to fetch.
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet#getsheetbynamename
So you can modify your function to take sheet name as input and then delete rows in that sheet. Then call your function with desired sheet names. Something like this:
var spreadSheet = SpreadsheetApp.openById("sheetID");
var DELETE_VAL = "abc";
var COL_TO_SEARCH = 4; // The column to search for the DELETE_VAL (Zero is first)
function deleteEachRow(sheetName){
var SHEET = spreadSheet.getSheetByName(sheetName);
var RANGE = SHEET.getDataRange();
var rangeVals = RANGE.getValues();
// existing logic
};
// Invoke deleteEachRow() for each sheet you want to delete the rows
["Sheet1", "Sheet2"].forEach((sheetName) => deleteEachRow(sheetName));
Umair is right, there was a simply error in the first line. But I'd want to add that the sheet.deleteRow(row) is not the best practice in case if there are many rows to delete. This command is quite time consuming.
If you have more than dozen rows to delete it's better to grab all data from a sheet (or range) var data = range.getValues(), clear the sheet (or the range), to process the array inside the script and refill the sheet back with new data new_range.setValues(array). It will work much faster.

Google app script exceeded execution time limit

I have a Google sheet with almost 160 sub sheets. All the questions are in "All" sheet and B column is the actual spreadsheet name where they should be. The following code is reading data from "All" spreadsheet and sending them perfectly to the last row of desired spreadsheet perfectly but it is taking very long time! Probably because it has a lot of subsheets and is using getSheetByName again and again. Now I've stored all the sub sheets' name and ID in 'sheets' and 'sheetID' arrays at once. I'm thinking to compare between rangeValues[j][1] and sheetNames[k][0]. Below is the code and screenshot of the spreadsheet.
Is this a suitable way? Please help me to run the script faster!
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("All");
var rangeData = sheet.getDataRange();
var lastRow = rangeData.getLastRow();
var searchRange = sheet.getRange(1,1, lastRow, 8);
var curr_sheet;
function send() {
var rangeValues = searchRange.getValues();
var sheetNames = new Array();
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i=0 ; i<sheets.length ; i++) sheetNames.push( [ sheets[i].getName() ] );
//Logger.log (sheetNames.length);
var sheetID = new Array();
var sheetIDs = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i=0 ; i<sheetIDs.length ; i++) sheetID.push( [ sheetIDs[i].getSheetId() ] );
//Logger.log (sheetID.length);
for ( j = 0 ; j < lastRow; j++)
{
for ( k = 0 ; k < sheetNames.length ; k++) //
{
if (rangeValues[j][1] === sheetNames[k][0])
{
var targetSheet = ss.getSheetByName(sheetNames[k][0]); // This line is working but taking very long to complete
var targetSheet = ss.getSheetByID(sheetIDs[k][0]); // This line is not code just to show what I'm thinking to do.
targetSheet.getRange(targetSheet.getLastRow()+1, 1, 1, 8).setValues([rangeValues[j]]);
}
}
}
}
I believe your goal as follows.
You want to reduce the process cost of your script.
Modification points:
There is no method of getSheetByID in Class Spreadsheet. By this, I think that in your case, the following script can be removed.
var sheetID = new Array();
var sheetIDs = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i=0 ; i<sheetIDs.length ; i++) sheetID.push( [ sheetIDs[i].getSheetId() ] );
In your script, getSheetByName and getRange(###).setValues(###) are used in the loop. And also, even when the sheet is the same sheet, each row is put using setValues in the loop every row. In this case, the process cost will be high. I think that these situation might be the reason of your issue.
Fortunately, in your situation, all sheets are in the same Spreadsheet. So for reducing the process cost of your script, here, I would like to propose to put the values to each sheet using Sheets API. The flow of modified script is as follows.
Retrieve Spreadsheet and values.
This script is from your script.
Retrieve all sheets in the Spreadsheet.
Check whether the sheet names from the column "B" are existing.
Create the object for putting to each sheet using Sheets API.
Request the method of spreadsheets.values.batchUpdate of Sheets API using the created object.
When above points are reflected to your script, it becomes as follows.
Modified script:
Please copy and paste the following script. And, please enable Sheets API at Advanced Google services. And then, please run the script.
function send() {
// 1. Retrieve Spreadsheet and values. This script is from your script.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("All");
var rangeData = sheet.getDataRange();
var lastRow = rangeData.getLastRow();
var searchRange = sheet.getRange(1,1, lastRow, 8);
var curr_sheet;
var rangeValues = searchRange.getValues();
// 2. Retrieve all sheets in the Spreadsheet.
var sheets = ss.getSheets().reduce((o, e) => Object.assign(o, {[e.getSheetName()]: e}), {});
// 3. Check whether the sheet names from the column "B" are existing.
// 4. Create the object for putting to each sheet using Sheets API.
var data = rangeValues.reduce((o, e) => {
if (sheets[e[1]]) {
if (o[e[1]]) {
o[e[1]].values.push(e);
} else {
o[e[1]] = {range: `'${e[1]}'!A${sheets[e[1]].getLastRow() + 1}`, values: [e]};
}
}
return o;
}, {});
// 5. Request the method of spreadsheets.values.batchUpdate of Sheets API using the created object.
Sheets.Spreadsheets.Values.batchUpdate({data: Object.values(data), valueInputOption: "USER_ENTERED"}, ss.getId());
}
Note:
In this case, when you run the script, one API call is used. Because the method of spreadsheets.values.batchUpdate is used.
In my environment, when I tested above script and your script using a sample Spreadsheet, I could confirm the reduction of the process cost of about 70 % from your script.
References:
Advanced Google services
Method: spreadsheets.values.batchUpdate
reduce()
Try this:
Should be a lot faster
function send() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName("All");
var rg=sh.getRange(1,1,sh.getLastRow(),8);
var v=rg.getValues();
var names=[];
var sheets=ss.getSheets();
sheets.forEach(function(sh,i){names.push(sh.getName());});
v.forEach(function(r){
let idx=names.indexOf(r[1]);//column2
if(idx!=-1) {
sheets[idx].getRange(sheets[idx].getLastRow()+1,1,1,8).setValues([r]);
}
});
}

Use Google Apps Script to loop through the whole column

I am trying to loop through the whole row in my google sheet and copy some of the data from one sheet to another. The list will get longer over time.
More specifically: If input in column B equals "blue", than copy the values from column A and C into another sheet.
Do this for all columns till the end of the column.
Link to my spreadsheet: https://docs.google.com/spreadsheets/d/1xnLygpuJnpDfnF6LdR41gN74gWy8mxhVnQJ7i3hv1NA/edit?usp=sharing
The loop stops when the colour does not equal blue. Why?
As you can see I used a for loop. Is that even the way to go?
Can I do anything about the speed of the code execution?
Any comments, hints or help are highly appreciated.
Regards!
You had the input sheet named "List" and I named the output sheet "Output". And here's the code.
function condCopy()
{
var s = SpreadsheetApp.getActiveSpreadsheet();
var sht = s.getSheetByName('List')
var drng = sht.getDataRange();
var rng = sht.getRange(2,1, drng.getLastRow()-1,drng.getLastColumn());
var rngA = rng.getValues();//Array of input values
var rngB = [];//Array where values that past the condition will go
var b = 0;//Output iterator
for(var i = 0; i < rngA.length; i++)
{
if(rngA[i][1] == 'blue')
{
rngB[b]=[];//Initial new array
rngB[b].push(rngA[i][0],rngA[i][2]);
b++;
}
}
var shtout = s.getSheetByName('Output');
var outrng = shtout.getRange(2,1,rngB.length,2);//Make the output range the same size as the output array
outrng.setValues(rngB);
}
You have 2 options. The first is to use the standard query() function from Google Sheets to get the values. The downside here is that it is only a reference of the values. So you cannot reorder them, etc. To use this, place this in cell A1 and it will pull the Headers and retrieve the values from column A and C:
=QUERY(A:C, "select A, C where B = 'blue'", 1)
For a Google Apps Script answer:
This will loop through your List sheet and for every row where column B is blue it will save the values in column A and C to column A and B of the new sheet:
function doIt(){
var activeSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet4");
var lastRow = activeSheet.getLastRow();
var lastCol = activeSheet.getLastColumn();
var targetValues = [];
var sourceSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("List");
var lastSourceRow = sourceSheet.getLastRow();
var lastSourceCol = sourceSheet.getLastColumn();
var sourceRange = sourceSheet.getRange(1, 1, lastSourceRow, lastSourceCol);
var sourceData = sourceRange.getValues();
var activeRow = 0;
//Loop through every retrieved row from the Source
for (row in sourceData) {
//IF Column B in this row has 'blue', then work on it.
if (sourceData[row][1] === 'blue') {
//Save it ta a temporary variable
var tempvalue = [sourceData[row][0], sourceData[row][2]];
//then push that into the variables which holds all the new values to be returned
targetValues.push(tempvalue);
}
}
//Save the new range to the appropriate sheet starting at the last empty row
activeSheet.getRange(lastRow + 1, 1 , targetValues.length, 2).setValues(targetValues);
}
Of course, you could pass the value to test to the function by replacing 2 lines. The first, defining the function:
function doIt(testingvar){
to pass a variable called testingvar, and the test line to replace the hard coded test with the passed variable:
if (sourceData[row][1] === testingvar) {

Data copying with Google Apps Script

I am trying to write a script in Google Apps Script that takes cell information from one sheet and copies it to another sheet, both for just grabbing certain columns to display on the second sheet and also a condition based on the values inside cells in a certain column. Here is what I have so far:
function onMyEdit() {
var myMaster = SpreadsheetApp.openById("xxxxx");
var masterSheet = myMaster.setActiveSheet(myMaster.getSheets()[0]);
var myNames = SpreadsheetApp.openById("xxxxx");
var namesSheet = myNames.setActiveSheet(myNames.getSheets()[0]);
var row1 = masterSheet.getRange(1, 1, masterSheet.getLastRow(), 1);
var rowV = row1.getValues();
var firstArray = masterSheet.getDataRange().getValues();
var dataList = [];
for (var i = 1; i < rowV.length; i++) {
dataList.push(firstArray[i][0]);
dataList.push(firstArray[i][1]);
dataList.push(firstArray[i][2]);
dataList.push(firstArray[i][3]);
}
for (var j = 0; j < rowV.length - 1; j++) {
namesSheet.getRange(2, j + 1, 1, 1).setValue(dataList[j]);
}
}
So as of now it only works on one row, starting from the second row (to allow for column headers). And I suppose when I want to grab rows conditionally based on cell data, I will use an 'if' statement for the condition inside the 'for' loop, but I want the data to copy to the next available row in both sheets. I suppose I'd use something like:
' getLastRow + 1 '
or something like that. I need this code to be as efficient as possible because of the amount of data and its purpose. I am pretty new to programming so please explain in detail, and thanks again.
I'm not sure I understood exactly what you wanted to do but -from what I understood- this code snippet should give you a better way to start with...
(I added a few comments to explain in the code itself)
function onMyEdit() {
var myMaster = SpreadsheetApp.openById("MasterSheet ID");
var masterSheet = myMaster.getSheets()[0]; // get 1rst sheet
var myNames = SpreadsheetApp.openById("NamesSheet ID");
var namesSheet = myNames.getSheets()[0]; // get 1rst sheet
var firstArray = masterSheet.getDataRange().getValues();
var dataList = [];
for ( r = 1; r < firstArray.length; r++) { // iterate the first col of masterSheet
if(firstArray[r][0]=='some condition'){ // if value in the first column == 'some condition get the second column cell in the new array (here you could change what you want to get)
dataList.push([firstArray[r][1]])
}
}
Logger.log(dataList)
if(dataList.length>0){
namesSheet.getRange(1,namesSheet.getLastColumn()+1,dataList.length,1).setValues(dataList);//copy data in a column after last col
}
}

Categories

Resources