I have a sheet where when I change a specific cell to "YES", I need a template sheet to be copied to a new version and named as per the value of a cell on the current row.
I'm having trouble working out how to get the value of the first cell in the row selected. This is what I have so far (I know this is wrong):
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var currentCell = sheet.getCurrentCell();
if (currentCell = "YES")
{
SpreadsheetApp.getActiveSpreadsheet().toast("New change control sheet added to workbook.","Change Control",15);
var sourceRow = ss.getActiveRange().getRowIndex();
var tabName = ss.getRange(cell,1).getValues();
ss.getSheetByName("CCTemplate").showSheet()
.activate();
ss.setActiveSheet(ss.getSheetByName('CCTemplate'), true);
ss.duplicateActiveSheet();
ss.setActiveSheet(ss.getSheetByName('CCTemplate'), true);
ss.getActiveSheet().hideSheet();
ss.setActiveSheet(ss.getSheetByName('Copy of CCTemplate'), true);
ss.getActiveSheet().setName("CC" & tabName);
}
}
Any ideas?
function onEdit(e) {
var sh=e.range.getSheet();
if(sh.getName()=='Your Sheet Name' && e.value=="YES") {
e.source.toast="New change control sheet added to workbook.","Change Control",15);
var tabName=sh.getRange(e.range.rowStart,1).getValue();
var tsh=e.source.getSheetByName('CCTemplate');
var csh=tsh.copyTo(e.source);
csh.setName('CC'+tabName);
}
}
You should avoid using activate in your scripts especially in simple triggers where you have to finish in 30 seconds. I think this code does the same thing that you intended for your code. One significant difference is that I use the information that comes in the event object that comes with the trigger. You should add the code Logger.log(JSON.stringify(e)) and then look at the logs you will see that there is a lot of information available to you which removes the need to run extra functions to get things like a spreadsheet.
Use event objects
onEdit offers among others the event objects range and value which are helpful to retrieve the range that has been edited and its value.
Also
When you want to a cell and compare it against a value, like in if (currentCell = "YES") - you need to retrive its value (either currentCell.getValue() or just event.value) and you need to use == instead of = for comparison.
Be careful with getValues() vs getValue(). The former gives you a 2D array and is not necessary if you want to retrieve the value of a single cell.
There is no need to set your sheet to active in order to change its name.
You can rewrite your code as following:
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var currentCell = event.range;
var value = event.value;
if (value == "YES")
{
...
var sourceRow = range.getRowIndex();
var tabName = ss.getRange(sourceRow, 1).getValue();
...
ss.getSheetByName('Copy of CCTemplate').setName("CC" + tabName);
}
}
Related
Please see example picture below.
I would like to use Google Sheets script editor to add a Filter view on cell B3, that filters on a custom formula =(B4=$B$1).
The plan is to add a onEdit function that checks whether the value in cell B1 has changed, and if so reset the filter view in cell B3.
This is breaking my brain! I can't seem to find a way to auto update/refresh the filter once my value in B1 has changed.
My code currently looks like this, but this doesn't re-add the custom formula. It just keeps showing me all the non-empty rows.
function onEdit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet()
var sheetname = "Lactation";
var sheet = ss.getSheetByName(sheetname);
var erow = e.range.getRow();
//Logger.log("DEBUG: row = "+erow);
var ecolumn = e.range.getColumn();
//Logger.log("DEBUG: column = "+ecolumn);
if (erow == 1 & ecolumn == 2 ){
// there is a match so the cell is B2
//Logger.log("DEBUG: the cell is B2");
updatefilter();
}
else
{
// there is no match so the cell is NOT I3
//Logger.log("DEBUG: the cell is not I3");
}
}
function updatefilter() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('B3').activate();
var criteria = SpreadsheetApp.newFilterCriteria()
.setHiddenValues(['', 'No'])
.build();
spreadsheet.getActiveSheet().getFilter().setColumnFilterCriteria(2, criteria);
};
You want to modify the basic filter of the cells "B3:B" when the cell "B1" on the sheet Lactation is edited.
In this case, you want to use the OnEdit event trigger.
You want to use =(B4=$B$1), which is the custom formula, as the filter condition.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Modification points:
You can take advantage of the event object as e of onEdit(e).
In order to use the custom function for the basic filter, you can use whenFormulaSatisfied.
Modified script:
In order to run the script, please edit the cell B1 on the sheet Lactation.
function onEdit(e){
var sheetName = "Lactation";
var editCell = "B1";
var range = e.range;
var sheet = range.getSheet();
if (range.getA1Notation() == editCell && sheet.getSheetName() == sheetName) {
updatefilter(sheet);
} else {
// do something
}
}
function updatefilter(sheet) {
sheet.getRange('B3').activate();
var criteria = SpreadsheetApp.newFilterCriteria().whenFormulaSatisfied("=(B4=$B$1)").build();
var filter = sheet.getFilter();
if (!filter) {
filter = sheet.getRange("B3:B").createFilter();
}
filter.setColumnFilterCriteria(2, criteria);
};
In this script, when the cell "B1" on the sheet of Lactation is edited, the function updatefilter is run by the OnEdit event trigger. And when the basic filter is existing, it is updated. When no filter is existing, the filter is set as new filter. The range of the filter is "B3:B". The custom function of =(B4=$B$1) is used as the criteria.
Note:
In above script, the simple trigger can be used. But if you use the methods for requiring to authorize, when an error occurs, please try to use Installable Triggers.
References:
Simple Triggers
Installable Triggers
Event Objects
whenFormulaSatisfied(formula)
If I misunderstood your question and this was not the direction you want, I apologize.
I've got a list of hyperlinks stored in a range and I would like to have the data validation in a different column allow you to choose from the link "text" and then paste the full cell formula. The way it works at the moment the data validation cell only contains the cell value.
Link to example sheet
Some sheet function code:
C2: =HYPERLINK("#rangeid=901904080","Cell E2")
C3: =HYPERLINK("#rangeid=951806747","Cell E3")
C4: =HYPERLINK("#rangeid=743771733","Cell E4")
Auto recorded script code:
function DV() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('C2').activate();
spreadsheet.getRange('A2:A').setDataValidation(SpreadsheetApp.newDataValidation()
.setAllowInvalid(false)
.requireValueInRange(spreadsheet.getRange('Sheet2!$C$2:$C'), true)
.build());
};
Is there something I can do to the script code to make it paste the formula in stead of the value? Or is the best option to try and trigger on when a new value is chosen and then do something like:
Att! Pseudo code
onEdit(e){
//pseudo code
if e.range in column A{
var sourceRange = sheet.getRange("C2:C")
var index = sourceRange.indexOf(e.range.getValue())
e.range.setFormula(sourceRange[index].getFormula())
}
}
It's a bit tricky, but I think I found the solution using the DataValidationBuilder. It's similar to yours but using getFormulas() along with the validation property requireValueInList() which asks for an array of Strings.
The main issue is when you select the formula, the "invalid rule" notification jumps in, as the formula changes the cell value to "Cell C3", for example. So in order to work around this paradox you need set setAllowInvalid(boolean) to false:
function DV(){
var spreadsheet = SpreadsheetApp.getActive();
var cells = spreadsheet.getRange('A2:A');
var rule = SpreadsheetApp.newDataValidation()
.requireValueInList(spreadsheet.getRange('C2:C').getFormulas())
.setAllowInvalid(false)
.build();
cells.setDataValidation(rule);
}
As the 500 items limit isn't acceptable in my case I have solved the problem by checking for a data validation rule on an edited cell and if one exists, swap the value for the data.
MWE:
function onEdit(e){
if (e.range.getDataValidation() != null){
swapValueForFormula(e.range);
}
function swapValueForFormula(cell){
var val = cell.getValue();
var rule = cell.getDataValidation();
var source = rule.getCriteriaValues()[0];
var vals = source.getValues();
var index = vals.join().split(",").indexOf(val);
if (index != -1){
var formula = source.getCell(index+1, 1).getFormula()
cell.setFormula(formula)
}
}
Thanks for all your comments and suggestions that lead me to this answer.
In google apps script, with onEdit trigger, how do I get the old values of all the cells if the edited range has multiple cells? In the Event object documentation https://developers.google.com/apps-script/guides/triggers/events it is specified that the oldValue attribute is "Only available if the edited range is a single cell".
In my case, I use onEdit from the Event object to run a function (that needs oldValue and newValue) only when a specific column is edited. It works fine when the user selects only one cell in my specific column, but if the user selects a few cells or the entire row for example, only data from the first selected cell is retrieved but I need to access the oldValue of my specific column.
You want to retrieve old values when the multiple cells are edited.
If my understanding is correct, how about this answer?
Issue:
Unfortunately, in the current stage, when the multiple cells are edited, there are no methods for retrieving all old values from the event object.
Workaround:
So as the current workaround, how about this flow?
Copy the active Spreadsheet.
This is run only one time.
When the cells are edited, the old values are retrieved by comparing the active Spreadsheet and copied Spreadsheet.
Update the copied Spreadsheet.
By above flow, the cycle for retrieving old values when the cells are edited can be created. When this flow is reflected to the script, it becomes as follows. Please think of this as just one of several answers.
Sample script:
When you use this script, please install the OnEdit event trigger to the function of onEditByTrigger() after copy and paste this script to the script editor of the container-bound script. By this, when the cells are edited, you can see the current and old values at the log.
var backupfilename = "backupfile";
function copyToo(srcrange, dstrange) {
var dstSS = dstrange.getSheet().getParent();
var copiedsheet = srcrange.getSheet().copyTo(dstSS);
copiedsheet.getRange(srcrange.getA1Notation()).copyTo(dstrange);
dstSS.deleteSheet(copiedsheet);
}
// This is run only one time.
function init() {
// Source
var srcss = SpreadsheetApp.getActiveSheet();
var range = srcss.getDataRange().getA1Notation();
var srcrange = srcss.getRange(range);
var srcsheetname = srcss.getName();
// Destination
var backupfile = DriveApp.getFilesByName(backupfilename);
var dstid = backupfile.hasNext()
? backupfile.next().getId()
: SpreadsheetApp.create(backupfilename).getId();
var dstss = SpreadsheetApp.openById(dstid).getSheets()[0]
var dstrange = dstss.getRange(range);
dstss.setName(srcsheetname);
copyToo(srcrange, dstrange);
PropertiesService.getScriptProperties().setProperty('backupfileid', dstid);
return dstid;
}
function onEditByTrigger(e) {
var columnNumber = 1; // If you want to retrieve the old values when the column "A" is edited, it's 1.
var source = e.source;
var range = e.range;
var dstid = PropertiesService.getScriptProperties().getProperty('backupfileid');
if (!dstid) {
dstid = init();
}
if (e.range.columnStart == columnNumber) {
var range = source.getSheetName() + "!" + range.getA1Notation();
var fields = "sheets(data(rowData(values(formattedValue,userEnteredFormat,userEnteredValue))))";
var currentValue = source.getRange(range).getValues();
var oldValue = SpreadsheetApp.openById(dstid).getRange(range).getValues();
Logger.log("currentValue %s", currentValue)
Logger.log("oldValue %s", oldValue)
}
// Update backup file
var range = e.source.getDataRange().getA1Notation();
var srcrange = e.source.getRange(range);
var dstrange = SpreadsheetApp.openById(dstid).getSheets()[0].getRange(range);
copyToo(srcrange, dstrange);
}
Note:
This is a sample script for retrieving the old values when the multiple cells were edited. So if you use this script, please modify this to your situation.
References:
Event Objects
Installable Triggers
If this was not the direction you want, I apologize.
I have poked around and found the following code that will advance to the last line on data in our Google Spreadsheet- at least until we add more lines beyond row 297.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var range = ss.getSheets()[0].getRange("B297");
SpreadsheetApp.setActiveRange(range);
}
I am trying to figure out how to write this script so that it will go to the last line of data, regardless of the line number.
Is there a change that I can make to this code to accomplish this?
The method getLastRow() tells you the last row of a sheet that has data. This can be used to move the selection to that row. Another thing I changed in the sheet selection: your script always operates on the first sheet; it makes more sense to operate on whichever sheet is active currently.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange(sheet.getLastRow(), 1);
SpreadsheetApp.setActiveRange(range);
}
This can be placed into the spreadsheet menu using onOpen.
By the way, pressing Ctrl + ArrowDown does the same thing, if you do it in a column that has some data in every row (like the date or Id column).
The script below allows you to go to the last cell with the content of column A. It works even if some cells in the column A contain formulas.
Modifying the number in parentheses in lastRowOfCol(1) allows you to reach the last cell with content from another column.
Additionally, you can also change the focus to the first empty cell after the last one with content.
function onOpen(){
lastRowOfCol(1); //Enter the column number you want to use as the base of the search
}
function lastRowOfCol(column){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var total = sheet.getMaxRows();
var values = SpreadsheetApp.getActiveSheet().getRange(1,column,total).getValues();
for(var i=total-1;values[i]=="" && i>0; i--){}
var last = sheet.getRange(i+1,column);
//var last = sheet.getRange(i+1,1); //Option to fetch the last row of a given column, but to position in column 1
//var last = sheet.getRange(i+2,column); //Option to go to the cell below the last one with content
sheet.setActiveSelection(last);
}
Script from Marcelo Camargo in this forum
The currently relevant option that works on all non-hidden sheets, and not just on the active one:
function onOpen() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const sheets = ss.getSheets()
const properties = PropertiesService.getDocumentProperties()
const lastActiveSheetName = properties.getProperty("lastActiveSheetName")
let lastActiveSheet
for (let sheet of sheets) {
if (!sheet.isSheetHidden()) {
const sheetName = sheet.getName()
const lastEdit = properties.getProperty(sheetName)
if (lastEdit) {
if (sheetName !== lastActiveSheetName){
sheet.getLastRow() // Without this magic does not work - I could not figure out the reasons
sheet.getLastColumn() // Without this magic does not work - I could not figure out the reasons
const [lastRow, lastCol] = lastEdit.split(',')
sheet.getRange(Number(lastRow), Number(lastCol)).activate() // With focus set to this cell
//sheet.setActiveSelection(sheet.getRange(Number(lastRow), Number(lastCol))) // Without setting focus to this cell
}
else {
lastActiveSheet = sheet
}
}
}
}
if(lastActiveSheet){
lastActiveSheet.getLastRow()
lastActiveSheet.getLastColumn()
const [lastRow, lastCol] = properties.getProperty(lastActiveSheetName).split(',')
lastActiveSheet.getRange(Number(lastRow), Number(lastCol)).activate()
}
}
function onEdit() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const sheet = ss.getActiveSheet()
if (!sheet.isSheetHidden()) {
const cell = ss.getActiveCell()
const row = cell.getRow()
const column = cell.getColumn()
if (row !== 1 || column !== 1) { // Protection from the evil magic of "self-editing" the first cell
const sheetName = sheet.getName()
PropertiesService.getDocumentProperties().setProperty(sheetName, `${row},${column}`)
PropertiesService.getDocumentProperties().setProperty("lastActiveSheetName", sheetName)
}
}
}
PS: Please note that in the code I do not use a semicolon separator - it's more convenient for me.
At the moment this is the function I'm using but I have no way of testing if it will work in the spreadsheet until I publish the application.
function readSelection() {
//The commented lines aren't needed if the sheet is open already
//var sheetid = "sheet id here";
//var spreadsheet = SpreadsheetApp.openById(sheetid);
//SpreadsheetApp.setActiveSpreadsheet(spreadsheet);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
//sheet.setActiveSelection("B2:B22");
var activerange = sheet.getActiveRange();
var activecells = activerange.getValues();
return activecells;
};
I assume you mean highlighted == selected
The result depends on whether the cells are contiguous or not (non contiguous cell selection is available in the new spreadsheets features http://googleblog.blogspot.co.nz/2013/12/new-google-sheets-faster-more-powerful.html
For contiguous cells selected your code returns the values of the selection as an array, for non-contiguous cells your code will return the an array with the single value of the LAST selected item.
I suggest that this is a bug in the implementation of the new spreadsheet. If it is important to you, I suggest you raise an issue. For the old spreadsheets, you can only select contiguous cells (eg B2:B22) so it will work as you expect.
The easiest way to answer this Q is to run the code you have written! You don't have to publish anything just run the code in the script editor of the spreadsheet you are examining
and look at the log.
function readSelection() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var activerange = sheet.getActiveRange();
var activecells = activerange.getValues();
Logger.log(activecells)
return
};
There is no way to do this at the moment or to obtain the selected ranges from a script.
A request is pending and you can support it here : https://code.google.com/p/google-apps-script-issues/issues/detail?id=4056
by adding a star to the request.
If/when this function is implemented your code would look as follows:
function readSelection() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var activeranges = sheet.getSelectedRanges();
var activecells = [] ;'
for (var ar in activeranges)
activecells = activecells.concat(activeranges[ar].getValues()) ;
Logger.log(activecells)
return ;
}
note that selected ranges may overlap, so some cell contents could be added twice.