I set up some code to copy and paste a certain value from the formula using form submissions as a trigger. It worked! But now it's giving me a "too many simultaneous invocations" error with reference to line 3.
It has not been called in excess of 20 times a day (as I know is the set limit) so I'm imagining I did something off with my code... (I'm NOT a JS guy.)
function pasteValue(){
var sheet =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('INVOICES')
var lastRow = sheet.getDataRange().getValues();
lastRow.forEach(function (row,index) {
if (row[1] == "") {
lastRow.length = index;
}
});
var newRange = sheet.getRange(lastRow.length,13);
newRange.copyTo(newRange, {contentsOnly: true})
sheet.setActiveRange(newRange);
}
If what you want is to remove the formulas, its more straightforward to do something like this with "display values":
function pasteValue(){
var sheet =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('INVOICES')
var data = sheet.getDataRange().getDisplayValues();
sheet.getRange(1, 1,data.length, data[0].length).setValues(data);
}
Related
function dataManp() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var copySheet = ss.getSheetByName("My-Sheet-1");
var pasteSheet = ss.getSheetByName("My-Sheet-2");
var clearContentRange = pasteSheet.getRange("A1:Z100");
clearContentRange.clearContent();
var source = copySheet.getRange("a1:f100");
var destination = pasteSheet.getRange("a1:f100");
source.copyTo(destination, {formatOnly:true , contentsOnly:true});
source.copyTo(destination,SpreadsheetApp.CopyPasteType.PASTE_COLUMN_WIDTHS,false);
var rows = pasteSheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (row[3] == '') {
var deleteRowNum = (parseInt(i)+1) - rowsDeleted
pasteSheet.deleteRow(deleteRowNum);
// temp_array[i] = i
rowsDeleted++;
}
}
pasteSheet.deleteColumn(2)
}
Hi,
I have written the following script to perform the following operations,
Copy data from My-Sheet-1 and paste to My-Sheet-2
Deletion of rows that corresponds to empty cells in column 3.
After that deletion of column 2
Rigthnow, the My-Sheet-1 contains only 60 rows and 20, the script is taking approximately 7 secs to complete. However in future the number of rows may extend to some 1000. Is there a way to optimize the above code, so that it takes less time to complete.
My observation is that, copy and pasting the data takes just milli secs. The major time consuming part are the operations, I am performing in the pasteSheet after pasting it. It may be helpful, if we can copy My-Sheet-1 to a temporary variable (copy everything including the formulas, format specifications, values, text etc..) and perform all operations in the temporary variable and then paste everything in the temporary variable to the desired target sheet. But, I don't know, how to copy everything in a sheet to a temporary variable, also, I am not sure, whether this will reduce the time or not. I would be glad, if I can get some help on this as well (i.e. copying everything in a sheet to a temporary variable, perfrom operations on the variables and then paste data in the variable to a new sheet)
Thank you
Edit - 1
Would like to add that, My-Sheet-1 contains mixed data (i.e. numerics, color formatted text, formulas in some cells etc)
Explanation:
deleteRow() takes some time per execution, so it's not recommended to use on hundreds of rows in a loop.
Simple answer would be:
Make a 2D array for Sheet1 using getValues().
Delete / filter out array elements depending if row2 is blank.
Use setValues() to write the filtered array into Sheet2.
Sample Code:
function dataManp() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var copySheet = ss.getSheetByName("My-Sheet-1");
var pasteSheet = ss.getSheetByName("My-Sheet-2");
var lr = copySheet.getLastRow();
var clearContentRange = pasteSheet.getRange(1,1,lr,26);
clearContentRange.clearContent();
var source = copySheet.getRange(1,1,lr,6);
var destination = pasteSheet.getRange(1,1,lr,6);
source.copyTo(destination, {formatOnly:true , contentsOnly:true});
source.copyTo(destination,SpreadsheetApp.CopyPasteType.PASTE_COLUMN_WIDTHS,false);
destination.clearContent();
var values = source.getValues();
var temp_array = [];
for (var i = 0; i < lr; i++) {
var rowValue = values[i];
if (rowValue[2] != '') {
temp_array.push(rowValue);
}
}
var newDest = pasteSheet.getRange(1,1,temp_array.length,6)
newDest.setValues(temp_array);
pasteSheet.deleteColumn(2);
}
One caveat is that you need to have the same format for all rows in a column.
Sample Input:
Sample Output:
I am using this script function to check if my cell functions in sheet has any errors or not.
Here is the code but it does not seems to be working. It keeps on saying no error when i have an error in a cell
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourcename = "Sheet1";
var source = ss.getSheetByName(sourcename);
var cell = source.getRange("A1:AG30");
function isError2(cell) {
const errorValues = ["#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A","#ERROR!"];
if (errorValues.includes(cell) != true) {
Logger.log("no error");
} else{
Logger.log("some error");
}
}
function isError2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourcename = "Sheet1";
var source = ss.getSheetByName(sourcename);
var cell = source.getRange("A1:AG30");
const errorValues = ["#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A","#ERROR!"];
if (errorValues.includes(cell) != true) {
Logger.log("no error");
} else{
Logger.log("some error");
}
}
Updated the approach but still having no luck with the desired output
var mysheet = SpreadsheetApp.getActive().getSheetByName('Sheet1');
var sheet1 = SpreadsheetApp.setActiveSheet(mysheet);
function findErrors(sheet) {
const errorValues = ["#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A","#ERROR!"];
var singleSheetArray = [];
var name = sheet1.getName();
// how many cells in the sheet currently
var maxRows = sheet1.getMaxRows();
var maxCols = sheet1.getMaxColumns();
var totalCells = maxRows * maxCols;
// how many cells have data in them
var r = sheet1.getLastRow();
var c = sheet1.getLastColumn();
var data_counter = r * c;
if (data_counter !== 0) {
var dataRange = sheet1.getRange(1,1,r,c);
var dataValues = dataRange.getValues();
dataValues.forEach(function(row) {
row.forEach(function(cell) {
if ((errorValues.indexOf(cell) === -1) ) {
SpreadsheetApp.getUi().alert("no errors in "+cell);
data_counter --;
}
});
});
}
}
Problem
Unable to check whether the cell has an error
Explanation
The issue you are facing is a simple type mismatch. getRange() method returns an instance of Range, while you try to compare it to a member of a errorValues array, which consists of strings. Therefore, errorValues.includes(cell) will always be false, hence first block of the conditional statement executing.
Solution
Use getValues() on the range, it will return you a 2-dimensional array of values. If you are only interested in one row (which you probably are), extract it and loop over the cells with some (or every) method, doing the same comparison.
Notes
On using global variables in custom functions and in GAS in general. You can use them, GAS environment is a JavaScript runtime with a convenience layer that simplifies working with Google APIs, nearly everything that's valid in JS is valid here. That being said, do treat global variables as if they don't exist - unless you know exactly what you are doing.
References
getRange method reference
getValues method reference
Custom functions guide
every method reference on MDN (see some there)
Try to move the 4 variables inside your function. Apps script does not support global variables. So the function does not recognize the var cell.
EDIT: Detect formula errors in Google Sheets using Script
My script is timing out, I guess because it's going over the 6 minute limit. Does anyone have an idea how I can optimize it so it runs faster?
The purpose of this script is to copy all the data from one sheet to another sheet, and then fill down some formulas on the destination sheet.
The amount of data copied is quite large (20,000 rows and columns to R) but I don't think it's so much that it should time out? I'm very new to this, so any advice is appreciated!
Here's the script:
function copyPasteTo2ndSheet()
{
var copyURL = 'ThisIsTheOriginSheetURL';
var pasteURL = 'ThisIsTheDestinationSheetURL';
var ssCopy = SpreadsheetApp.openByUrl(copyURL);
var ssPaste = SpreadsheetApp.openByUrl(pasteURL);
var copySheet = ssCopy.getSheetByName('Orders');
var pasteSheet = ssPaste.getSheetByName('Orders');
var lr = copySheet.getLastRow();
var copyData = copySheet.getRange('A2:R'+lr).getDisplayValues();
var pasteLr = pasteSheet.getLastRow();
pasteSheet.getRange('A2:R'+pasteLr).clear();
pasteSheet.getRange('A2:R'+lr).setValues(copyData);
copyPasteFormula(pasteSheet)
}
function copyPasteFormula(sheet)
{
var formulas = sheet.getRange(2, 1, 1, sheet.getLastColumn()).getFormulas()[0];
var lr = sheet.getLastRow();
for(var i in formulas)
{
var formula = formulas[i];
if(formula !== '')
{
var j = parseInt(i)+1;
sheet.getRange(3, j, lr, 1).setFormula(formula);
}
}
}
From the experience that I have had, the sheet.getRange(3, j, lr, 1) line, that has to repeat for 20,000 cells is likely causing the issue.
Commands that get or set to the sheet often take longer and use more memory to run. As a result, it is recommended to either batch your gets or, in this case, you can likely copy an entire row paste it. Copying a formula and pasting it in a different cell will update cell references, just as it does in the UI.
I have this function which works but it gets all responses.
function setEditUrl(ss, createDateColumn)
{
var formURL = 'https://docs.google.com/forms/d/101bMiRw9TQaGbdDc4U_tLAD0QzicqejM9qXOEwJPQKU/viewform';
var urlColumn = createDateColumn-2;
var data = ss.getDataRange().getValues();
var form = FormApp.openByUrl(formURL);
for(var i = 2; i < data.length; i++)
{
if(data[i][0] != '' && data[i][urlColumn-1] == '')
{
var timestamp = data[i][0];
var formSubmitted = form.getResponses(timestamp);
if(formSubmitted.length < 1) continue;
var editResponseUrl = formSubmitted[0].getEditResponseUrl();
ss.getRange(i+1, urlColumn).setValue(editResponseUrl);
}//end of if
}//end of for
return;
}// This is the end of the setEditUrl function
As the spreadsheet gets larger I am concerned with performance lag so I want to streamline it and replace the function with one like the one below which just gets the editURL for the last response and only if the sheet cell is empty
function setGoogleFormURL(ss, lastRowInx, createDateColumn)
{
var urlColumn = createDateColumn-2;
if (ss.getRange(lastRowInx, urlColumn).getValue() == "") // so that subsequent edits to Google Form don't overwrite editResponseURL
{
var form = FormApp.openById('101bMiRw9TQaGbdDc4U_tLAD0QzicqejM9qXOEwJPQKU');
var formResponses = form.getResponses();
var lastResponseIndex = form.getResponses.length-1;
var lastResponse = formResponses[lastResponseIndex];
var editResponseUrl = lastResponse.getEditResponseUrl();
var createEditResponseUrl = ss.getRange(lastRowInx, urlColumn);
createEditResponseUrl.setValue(editResponseUrl);
}
else{} //do nothing
however this seems to break on the getEditResponseUrl. I am getting the following error TypeError: Cannot call method "getEditResponseUrl" of undefined. (line 100, file "Code").
I used #SandyGood 's answer to this post as a reference. I wonder though if her observation about the event trigger is why this is borking. This is the onFormSubmit function I am using to call this and other fucntions.
function onFormSubmit(e)
{
var ss = SpreadsheetApp.getActiveSheet();
var lastRowInx = ss.getLastRow(); // Get the row number of the last row with content
var createDateColumn = ss.getMaxColumns(); //CreateDateColumn is currently in AX (Column 50) which is the last/max column position
var createDate = setCreateDate(ss, lastRowInx, createDateColumn);
var trackingNumber = setTrackingNumber(ss, lastRowInx, createDateColumn);
//var editURL = setEditUrl(ss, createDateColumn);
var editResponseURL = setGoogleFormURL(ss, lastRowInx, createDateColumn);
}//This is the end of onFormSubmit
I also found a whole bunch of sources 234where they were looking use the URL to append to an email, were more complex than my use case, or were unanswered. I also found some solutions for getting the EditURL by binding the script to the form but since I want to store the value on the sheet it needs to be bound to the sheet rather than the form.
UPDATE:
Okay so I tried to bind my script to the form instead of the sheet which allowed me to see the URL but now I have the problem in reverse where the form can't find the spreadsheet methods like .getMaxColumns TypeError: Cannot find function getMaxColumns in object Spreadsheet. (line 40, file "Code") AND .getActiveRange Cannot find method getActiveRange(number). (line 48, file "Code").
Here is the code on the form side
function onFormSubmit(e)
{
var form = FormApp.getActiveForm();
var activeFormUrl = form.getEditUrl();
var ss = SpreadsheetApp.openById(form.getDestinationId());
var createDateColumn = ss.getMaxColumns(); //CreateDateColumn is currently in AY (Column 51) which is the last/max column position
var urlColumn = createDateColumn-1; //urlColumn is currently in AX (Column 50) Calculating using it's relative position to createDateColumn Position
Logger.log(activeFormUrl, createDateColumn, urlColumn);
var checkLog1 = Logger.getLog();
Logger.clear();
if (ss.getActiveRange(urlColumn).getValue() == "") // so that subsequent edits to Google Form don't overwrite editResponseURL
{
var editResponseURL = setGoogleFormEditUrl(ss, createDateColumn, activeFormUrl);
var createEditResponseUrl = ss.getActiveRange(urlColumn);
createEditResponseUrl.setValue(activeFormUrl);
}
else
{
if (ss.getActiveRange(urlColumn).getValue() != activeFormUrl)
{
Logger.log("Something went wrong - URL doesn't match")
Logger.log(ss.getActiveRange(urlColumn).getValue());
var checkLog2 = Logger.getLog();
}
else {}//do nothing
}
}//This is the end of the onFormSubmit function
So I am wondering how I can pass a variable between the form and the sheet. Can I somehow read the form log programmically from the sheet? Can I append the value to the form response array (This would mean a few other edits to the referenced columns but could work). Thoughts #Gerneio , #SandyGood , Anyone else?
UPDATE 2:
There seemed to be a conflict with using both the methods from the FormApp and the SpreadsheetApp within the same function.
The solution that worked for me was to modularize the spreadsheet functions out (except the getActiveSheet) and to leave the getEditResponseURL method within the onFormSubmit Function.
The code snippet can be found posted here.
I'd suggest trying to use the onFormSubmit(e) on the form side.
function onFormSubmit(e)
{
var form = e.source;
var response = e.response;
var sheet = SpreadsheetApp.openById(form.getDestinationId());
var editUrl = response.getEditResponseUrl();
Logger.log(editUrl); // check the logger to see what results you are getting now
// Then do whatever operations you need to do...
}
Update:
I'm not so sure why you are having so many problems with this, but I can tell you for sure that it can be done from either side, the Form or Spreadsheet. I just put together a working example with code written on the Spreadsheet side, none what-so-ever on the Form side. Check it out:
function onFormSubmit(e)
{
var rng = e.range;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var fUrl = ss.getFormUrl();
var f = FormApp.openByUrl(fUrl);
var rs = f.getResponses();
var r = rs[rs.length - 1]; // Get last response made
var c = getCellRngByCol(rng, 'Edit Response URL');
c.setValue(r.getEditResponseUrl());
}
// Specific for a form submit trigger
// Pass e.range and the name of the column
// to return a single cell
function getCellRngByCol(rng, col)
{
var aRng = SpreadsheetApp.getActiveSheet().getDataRange();
var hRng = aRng.offset(0, 0, 1, aRng.getNumColumns()).getValues();
var colIndex = hRng[0].indexOf(col);
return SpreadsheetApp.getActiveSheet().getRange(rng.getRow(), colIndex + 1);
}
There were a few small hiccups that I ran into. Firstly, make sure to setup the trigger accordingly. I highly recommend setting up immediate notifications of failures. Secondly, even though the function will rely on the event that is passed, manually run the onFormSubmit(e) method at least once before submitting a form. It will check to see if your script needs any authorization and will request if needed. I'd also recommend that you open up a new form, link a fresh new spreadsheet, and test this code to make sure it works. Then mold the above code to fit your needs.
If you can't get it, then I'll share a working example.
There seemed to be a conflict with using both the methods from the FormApp and the SpreadsheetApp within the same function.
The solution that worked for me was to modularize the spreadsheet functions out (except the getActiveSheet) and to leave the getEditResponseURL method within the onFormSubmit Function.
The code snippet can be found posted here.
please can anyone help with one problem in google spreadsheet?
After changing value in one concrete collumn in sheet "Venues", I would like to write log about name and time, when this value was changed. But I can't really realize , if I am working with spreadsheet "Venues" or some other. I am not very into class structure of google API for Spreadsheet. So can anyone help with it?
I need:
run eventhandler on event when value in appropriate column in appropriate sheet ("Venues") is changed
get value from collumn name from this sheet
get actual time
write name and time to another sheet called "status_history" to last row (like append)
My hard try to write something: (but that is really bad code)
function onEdit(event)
{
var sheet = event.source.getActiveSheet();
var cell = sheet.getActiveCell();
var cellR = cell.getRow();
var cellC = cell.getColumn();
var cellValue = cell.getValue();
var cellCName = cell.getColumn()-1; //column with names
var name = sheet.getRange(cellR, cellCName).getValue();//get name
var active_spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
if(sheet.getName() == "Venues"){
if(cellC == 5 /* if correct collumn was changed */){
var output_sheet = active_spreadsheet.getSheetByName("status_history");
var lastRow = output_sheet.getLastRow();
var lastRange = output_sheet.getRange(lastRow, 1)
//HERE: write value: name
var lastRow = output_sheet.getLastRow();
var lastRange = output_sheet.getRange(lastRow, 2)
//HERE: write value: time
}
}
}
You were getting there. Just a couple of tweaks needed.
With onEdit functions, you need to keep things fast, since they get invoked so often.
Rely on the event information as much as you can, avoiding calls to Google Apps services.
If you must use a service, do it only when you absolutely need to - for example, wait until you are past the if statements that tell whether you are in a cell you want to log before calling SpreadsheetApp.getActiveSpreadsheet().
The API is rich, so look for functions that will let you reduce the number of system calls you make - see how appendRow() replaced multiple statements, for example.
Here's your function after a code inspection:
function onEdit(event) {
var sheet = event.range.getSheet();
if(sheet.getName() == "Venues"){
// correct sheet
var cell = event.range;
//var cellR = cell.getRow(); // not used at this time
var cellC = cell.getColumn();
var cellValue = event.value;
if (cellC == 5) {
// correct column
var name = cell.offset(0,-1).getValue(); // get name, 1 column to left
var time = new Date(); // timestamp
var active_spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var output_sheet = active_spreadsheet.getSheetByName("status_history");
output_sheet.appendRow([name,time]);
}
}
}
You could make it more flexible and portable by using column names to test conditions. Take a look at Adam's answer here.