I need to associate information with a row in a Google Sheet programmatically, with minimal user interaction. The association must stay associated with the same row, even when a row is added or deleted above. Also, the information should be deleted if the row is.
To me, this sounds like a job for DeveloperMetadata.
I would like to do this without the API for now - to avoid adding more variables. I would use named ranges before I would use the API.
Anyways, the Spreadsheet, Sheet, and Range classes all have the method: addDeveloperMetadata, with various inputs - none of which have a DeveloperMetadataLocationType input (problem #1).
Interestingly, as suggested by the answer here, the following code DOES add developer metadata to a column:
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange("H:H").addDeveloperMetadata("columnName", "price");
However, I can not for the life of me figure out how to create a "ROW" range to add metadata. I have tried the following:
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange(1, 1, 1, sheet.getMaxColumns()).addDeveloperMetadata("rowStatus", "processing");
and
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange("A2:L2").addDeveloperMetadata("rowStatus", "processing");
where L is the last column.
In both cases, I get the following error:
Adding developer metadata to arbitrary ranges is not currently
supported. Developer metadata may only be added to the top-level
spreadsheet, an individual sheet, or an entire row or column.
Is there any way to indicate that a range is a ROW, so I can add metadata to it?
And as soon as I post the question, I figure out the answer.
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange("2:2").addDeveloperMetadata("rowStatus", "processing");
Found the answer by pretending to create a Conditional Format Rule, and selected the row number when indicating the range to see how Google Sheets A1 notation represents rows.
Related
I'm using a piece of Google Script to copy from one Google Sheet to another Google Sheet, but also doing some column and data manipulation (so it's not just a straight copy).
The code was inspired by this question: Event trigger to move row to one of two other sheets based on values in 2 columns
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.range;
...
}
Unfortunately it seems that when you check Multiple checkboxes in one go by highlighting multiple cells and pressing the space bar, the OnEdit event object will only detect the first cell as being edited, and the other cells not being edited.
I tested this by doing:
Browser.msgBox(event.range.getNumRows())
No matter how many Checkboxes I edit in one go; it always returns 1
Is there a correct way to get the true range of Cells edited in one go?
This appears to be a bug!
I gave Google's Issue Tracker a look and it appears that this also happens when deleting multiple cells with checkboxes - only the range of the first cell is passed to e.range. Here's the report which details the same kind of behaviour:
Deleting a range of cells from a sheet passes only the range of Checkboxes in the deleted range to the onEdit(e) event object
It appears that this is an issue related to the way checkbox ranges are passed to the event object as well.
Google does seem to know about this issue but if it's causing problems you can file your own bug about it here as Diego mentioned above.
You can also hit the ☆ next to the issue number in the top left on the aforementioned pages which lets Google know more people are encountering this and so it is more likely to be seen to faster.
consider this situation.
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("2:2");
var values = range.getValues();
values[0][0] = "updated value"; //updating retrieved data
sheet.insertRowAfter(1); //here the problem appears
range.setValues(values); //trying to save updated row data - FAIL!
Updated row values will be written in a row number 2 (as per range selector "2:2") not in new position "3:3".
Result: It will overwrite data at row 2 instead of updating previously selected/loaded row, which is now at position "3:3" and not anymore at "2:2".
Im looking for a way how to keep "address" of a range even when the range was shifted because of added/removed rows/cells somewhere else in the sheet.
In this dummy example I can track range changes. But in parallel processing/updating sheet i can't simply track all changes from many different places.
So far I came up with a solution add new rows only at the end of SS (that doesn't change range of any rows above, but I would like to have ability add new rows on top of sheet without influencing already selected ranges.
Deleting rows is also dangerous situation - it changes position of ranges also.
LockService can't really solve the situation, because I have plenty scripts working on the same sheet (not using centralized library because of speed performance in addon).
Metadata for cell or row seems too complicated to handle for such an easy task.
From my point of view Object Range should keep its position even when its moved/shifted somewhere else. Otherwise I can't see reason to have Range as an Object - if it keeps only fixed information about from where it was picked.
Any advices are welcomed. Thank you in advance..
EDIT:
Just to add a context. I'm using Google sheets as a database for orders (10 thousands so far) - each row means one order (customer) and not all orders are in one sheet - different products have different sheets (+- 10 products/sheets)
There is an suggestion using named ranges to solve this issue - So what will happen if a spreadsheet will have ten of thousands named ranges - can that work without serious performance issues? Im thinking about to creating named range for each order row, so I can easily pick up right row by orderId and not to be afraid of moving a row when new order arrive during processing another one
You want to achieve the following flow.
Retrieve the value from "2:2" (row 2).
Modify the retrieved value.
Insert a row just below of the row 1.
By this, the initial row 2 is moved to the row 3.
Put the retrieved value to the row 3 which is the initial row 2.
You want to achieve this using Google Apps Script.
If my understanding is correct, how about this answer? Please think of this as just one of several answers.
Issue and solution:
In your script, range of var range = sheet.getRange("2:2"); is the constant. By this, even when the row is inserted, this value is not changed. This is the reason of your issue.
I think that the idea using the named range will be resolved for your situation. But your comment says as follows.
I just found, that namedRange is not capable actualize its own address, if something had changed in the sheet after initialize of a range.
When the row 2 is installed as the named range, when a row is inserted to just below of the row 1, the range of the named range is also changed. But from your comment, I thought that the named range might have not been reloaded.
In order to confirm this, I would like to propose a sample script.
Sample script:
Before you run the script, please install a named range to row 2 as the name of sampleNamedRange. Then, run this script.
var ss = SpreadsheetApp.getActiveSpreadsheet(); // Added
var sheet = ss.getActiveSheet(); // Modified
// Retrieve the range from the namedRange.
// namedRange of "sampleNamedRange" is installed for the range of "2:2" of the active sheet.
var range = ss.getRangeByName("sampleNamedRange"); // Modified
var values = range.getValues();
values[0][0] = "updated value"; //updating retrieved data
sheet.insertRowAfter(1);
// Retrieve the range from the updated namedRange.
var range = ss.getRangeByName("sampleNamedRange"); // Added
range.setValues(values);
Result:
Note:
The point is to retrieve the range from the updated named range. By this, the updated range can be used. So in above script, the updated value is put to the row 3.
References:
getRangeByName()
Class NamedRange
If I misunderstood your question and this was not the result you want, I apologize
The problem:
When using IMPORTRANGE to pull data from a different spreadsheet, cells in the origin spreadsheet that contain a formule containing VLOOKUP often (but not always) return #N/A (ERROR: Did not find value 'xxx' in VLOOKUP evaluation). In the origin sheet the formulas are calculated correctly and showing a value though. This does not always happen, sometimes it does pull in the proper value.
The intent:
To take data from different spreadsheets, combine them in a different spreadsheet and do some calculations on them. And then pull info from this calculation spreadsheet (or from multiple calculation spreadsheets) into a reporting spreadsheet for some further calculations and formatting.
The set-up:
There are several source data spreadsheets,say dataspreadsheet1, dataspreadsheet2 and dataspreadsheet3. A calculation spreadsheet (calcspreadsheet) is created that creates a copy of sheet1 in each of the data spreadsheets and names these sheets datasheet1, datasheet2 and datasheet3 respectively. The IMPORTRANGE statement used for this is created as follows: importrange(+VLOOKUP("dataspreadsheet1",filelist!A1:C1000,3,FALSE),"sheet1!a1:Z1000") where
filelist!A1:C1000 is a sheet in calcspreadsheet that contains Name, Type and Id in respective columns.
The values in each of these sheets datasheet1-3 are then used for calculations in another sheet, calcsheet1 in the calcspreadsheet. As the main goal of this is to add up daily values from the 3 dataspreadsheets, but those sourcesheets do not all have the same data on the same line a VLOOKUP is used again to make sure additions for a date use the line for the date in datasheet1-3 regardless of its row number. E.g. VLOOKUP($A11,'datasheet1'!$A:$P,4) + VLOOKUP($A11,'datasheet2'!$A:$P,4) + VLOOKUP($A11,'datasheet3'!$A:$P,4) where column A is the date column in all sheets.
This appears to work fine, although upon opening calcspreadsheet it can take a long time for it to go through an update, during which time lots of #N/A's are displayed. Eventually it comes right though.
The problem arise when a reportspreadsheet is created that in turn used an IMPORTRANGE call to pull the info from calcsheet1 in order to be able to work with it. This often, but not always, results in the problem states at the start. The IMPORTRANGE call in this reportspreadsheet is generated in a similar way as that in the calcspreadsheet: =importrange(+VLOOKUP(calc!B1,sheetcodes!A1:C3000,3,FALSE),"sheet1!a1:Z1000") where calc!B1 contains the name of the source spreadsheet (in this calc that would be 'calcspreadsheet' and sheetcodes!A1:C3000 again contains a list of sheets with Name, Type and Id in respective columns
A work-around I tried:
What I did notice that IMPORTRANGE works better on cells that do not contain VLOOKUP So I tried to copy the content of calcsheet to another sheet in calcspreadsheet, called exportsheet but having only the values in there, not formulas and then use IMPORTRANGE on this exportsheet. The copy script used is as follows:
function exportPrep() {
// Get the active spreadsheet and the active sheet
//var ss = SpreadsheetApp.getActiveSpreadsheet();
//var sheet = ss.getSheetByName("stream");
//sheet.activate();
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("calcsheet");
var sourceDataRange = source.getDataRange();
var sourceSheetValues = sourceDataRange.getValues();
var sourceRows = sourceDataRange.getNumRows();
var sourceColumns = sourceDataRange.getNumColumns();
var destination = SpreadsheetApp.getActiveSpreadsheet();
SpreadsheetApp.setActiveSheet(destination.setActiveSheet("exportsheet"));
destination.getDataRange().offset(0, 0, sourceRows, sourceColumns).setValues(sourceSheetValues);
}
This seemed to work, but unfortunately the copy script used to copy the value of calcsheet into exportsheet now showed the same behaviour, it would sometimes work and sometimes give the #N/A and so leaves me with the same problem.
My questions:
I've read various posts with similar issues and responses that mentioned this function was temperamental or had a bug. Other stated it is not possible to use dynamic references in IMPORTRANGE. Given that it sometimes works and sometimes not I suspect the function itself might be correct but that there are sync or time-out issues in the set-up.
How can I set up the above functionality. Either with the use of IMPORTRANGE and VLOOKUP at all with some changes/additions, or built in a different way from the get-go.
So I've not done this with importrange but I when I have this issue with Vlookup I wrap the vlookup in an IF and test for the #N/A.
Try something like: =IF(ISNA(VLOOKUP(...)),"",VLOOKUP(...))
=IFERROR(VLOOKUP(B81,'KAM_Q3_OPPS STAGE_(H/I/L/M/N)'!$F$3:$G$111,2,False),)
This is the best way I found to do it and the last two values [ , ) ] make the new output, in this case it is nothing.
IF you wanted 0 put in then you would do [ ,0) ]
I have been struggling with the following situation for almost the whole last week and it would be awesome if someone could give me some hint.
The situation:
1. Script finds a particular value in, lets say, 'Sheet1', and gets the row number of the cell containing this value.
2. Since script has found this value, it executes a bunch of actions like creating new spreadsheet and copying numerous 'Sheet1' from dozen other spreadsheets to this newly created spreadsheet, and comparing/ analyzing data.
The problem.
- While script is doing a bunch of other actions, user is able to rearrange cells by, for example, sorting A to Z, which changes the address of previously found cell.
Here is the code that I used in order to verify this:
function WhatHappensIfUserSorts () {
var ss = SpreadsheetApp.getActive();
var sh = ss.getActiveSheet();
var rng = sh.getRange("B17"); //Lets say that script finds this cell according to some rules
Utilities.sleep(10000);
rng.setValue("Test Value");
}
Question:
Might there be any workaround for this?
My current ideas.
1. I was thinking about hiding the filter row in the beginning of the script, but this doesn't help a lot, because users can insert new row in the which will change the addresses of the rows below.
The background.
I am trying to create two way synchronization, meaning, each project member has his/ her own spreadsheet with 'Project X', 'Project Y' etc. sheets and no matter who updates their project sheets, all other users that work on the same project get these updates in their project sheets. These updates that have to be tracked are not just the cell values, these are cell notes as well. And this is the reason why script has to do the bunch of other actions, since CopyTo method does not work between spreadsheets.
During my research I found sheetSpider project, but it seems somewhat different and too complicated from what I need.
A simple suggestion would be to give each row a unique identifier so that you could use it to evaluate the target range again before you write back to the sheet.
get target row's unique ID --> do work --> locate target rows ID and use to determine write range --> write back to sheet.
Alternatively, during the operation you could delete the target row and then use appendRow() to drop the updated version back in.
A third and final suggestion might be to temporarily suspend the permissions for the sheet. See: https://developers.google.com/apps-script/reference/spreadsheet/page-protection#setProtected
Please help this non-expert -- his job depends upon it.
I have a Google Form that feeds answers into a sheet on a Google Spreadsheet -- let's call it the "main sheet." One of the form's questions asks for a job number. I have been able to figure out how to take each unique job number respondents have entered and create a sheet by that name in the same spreadsheet. I even have copied the headers for the form's answers at the top of each of the new sheets.
What I cannot get to work is rifling through all of the answers in the main sheet, sans the column headers, and copy the rows to the sheet names based on a conditional match with the job number value equaling the sheet name value. So if someone applies for job number 65, that response gets copied from the main sheet to sheet with the name "65."
For starters, I may have the loops set up incorrectly, trying to exclude the header in the main sheet and creating the array of all of the sheet names.
But a second problem I have is that I need to use the variables for both the sheet name value and the job number value. I need to be able to account for an ever increasing job numbers. Staff do not want to have to create a new sheet with every new job number -- they want that done automatically when a user fills in a new job number.
I am happy to share my work, as it is, with anyone who can help point me in the right direction.
+++
Solved, so I yanked down the link to the spreadsheet. In spite of myself, it appears, this community was able to help me out. Thanks.
This function that I wrote will help you find your job number:
function searchColumn(value, rangeValues) {
for (var i = 1; i < rangeValues.length; ++ i) {
if (rangeValues[i] == value) return i;
}
return -1;
}
Give it an array of values (usually by Range.getValues()), and the value you want to search and it will return the row number where that value was found. If it didn't find it, it returns -1.
example:
// Gets all data in the first column of the sheet
var valuestoSearch = mySheet.getRange(1, 1, sheet.getLastRow()).getValues();
// then
var rowNum = searchColumn(job_number, valuesToSearch);
Now that you have the row number, you can use:
var rowData = mySheet.getRange(rowNum, 1, 1, mySheet.getLastColumn()).getValues();
This will give you all the values in that row up to the last column in the sheet.
Once you have that data, you can copy it to another sheet by using:
var jobSheet = mySpreadsheet.getSheetByName(job_number);
jobSheet.getRange(jobSheet.getLastRow() + 1, 1, 1, rowData.length).setValues(rowData);
Or something very similar to this. Then you may want to erase the data in the old spreadsheet so that you don't keep repeating the same operation. But that's up to you how you want to handle that. Take a good look at the documentation for SpreadsheetApp, as it has everything you're looking for and more :)