I'm writing a script for Google Sheets in App Script. The spreadsheet contains two sheets and each has a button on it. Clicking the button on the first sheet takes you to the second sheet. Clicking the button on the second takes you back to the first.
It sounds simple, but I'm unable to restore the cursor position when I go back to each sheet. It always changes to A1 instead. Here is my code.
function changesheet2() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Sheet2'), true);
};
function changesheet1() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Sheet1'), true);
};
According to the documentation, calling setActiveSheet with parameter true should restore the previous active selection and cursor position. This seems to work OK if you do everything within a single function. It doesn't work when there are two separate functions as here.
It's presumably something about context and who can see what data. I am running both scripts from the same user context by getting the user to click a button in each sheet. How can I make it work?
Note that it will only work if it is in the same function. Since the session still has the data where it was previously at. When the function stops, you don't have a way of knowing where was the previous selection was.
The sample code shows it merely returns to the previous activated cell of the sheet.
// Set the first sheet as the active sheet and select the range D4:F4.
spreadsheet.setActiveSheet(firstSheet).getRange('D4:F4').activate();
// Switch to the second sheet to do some work.
spreadsheet.setActiveSheet(secondSheet);
// Switch back to first sheet, and restore its selection.
spreadsheet.setActiveSheet(firstSheet, true);
What I can recommend you is create another sheet storing their previous active cells before the session ends.
Code:
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = spreadsheet.getSheetByName('Sheet1');
var sheet2 = spreadsheet.getSheetByName('Sheet2');
var record = spreadsheet.getSheetByName("record");
function changesheet() {
var sheet = spreadsheet.getActiveSheet();
var sheetName = sheet.getSheetName();
var selection = sheet.getActiveCell().getA1Notation();
if(sheetName == sheet1.getSheetName()){ // if in sheet1, go to sheet2
spreadsheet.setActiveSheet(sheet2);
var sheet2LastActiveCell = record.getRange("A2").getValue();
// if sheet2 was previously recorded, get it's last active cell
// else, go to A1 (default cell)
if(sheet2LastActiveCell)
sheet2.getRange(sheet2LastActiveCell).activateAsCurrentCell();
}
else if(sheetName == sheet2.getSheetName()){ // if in sheet2, go to sheet1
spreadsheet.setActiveSheet(sheet1);
var sheet1LastActiveCell = record.getRange("A1").getValue();
// if sheet1 was previously recorded, get it's last active cell
// else, go to A1 (default cell)
if(sheet1LastActiveCell)
sheet1.getRange(sheet1LastActiveCell).activateAsCurrentCell();
}
else // don't do anything if not in sheet1 or sheet2
return;
// record current cell of previous sheet
recordSheet(sheetName, selection);
}
function recordSheet(sheet, range){
if (sheet == sheet1.getSheetName())
// record sheet1 active cell in A1
record.getRange("A1").setValue(range)
else
// record sheet2 active cell in A2
record.getRange("A2").setValue(range)
}
Additional Notes:
You can hide the record sheet after creating it.
If not yet recorded, it will go to A1 by default.
getActiveCell will always get the active cell of the active sheet. It can't get the active cell of non-active sheets.
Related
I have a sheet with data that is imported through IMPORTRANGE to another sheet. When I make changes to Spreadsheet 1 >>> Spreadsheet 2 I have a script that copy the data from Copy to Paste. It is working all fine however I want to make sure that the script only runs when changes are made in the 'Copy' sheet and not any other. At the moment it runs independent on what sheet I make changes in.
Spreadsheet 1
Spreadsheet2
I tried onChange trigger two different ways...
This one makes the change but triggers when changes are made in any of the sheets
function Trigger2(e) {
var sheet = e.source.getActiveSheet();
if (sheet.getName() === 'Copy') {
copyInfo();
}
}
AND
(this does not work)
function Trigger2(e) {
var sheet = e.source.getActiveSheet();
var sheet = SpreadsheetApp.getActiveSpreadsheet();
if( sheet.getActiveSheet().getName() !== "Copy" ) return;{
copyInfo();
}}
The copy code looks like this...
function copyInfo() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var copySheet = ss.getSheetByName("Copy");
var pasteSheet = ss.getSheetByName("Paste");
// get source range
var source = copySheet.getRange(2,1,12,8);
// get destination range
var destination = pasteSheet.getRange(2,1,12,8);
// copy values to destination range
source.copyTo(destination);
}
Copy Changes from one Sheet To Another after Imported Range Changes
Spreadsheet 2 Imports a range to Sheet1 from Spreadsheet 1 Sheet1 and when a change occurs in Spreadsheet 2 Sheet1 we copy data from Spreadsheet2 Sheet1 to Spreadsheet2 Sheet 2.
function onMyChange(e) {
//Logger.log(JSON.stringify(e));//useful during setup
//e.source.toast("Entry");//useful during setup
const sh = e.source.getSheetByName("Sheet1")
Logger.log(sh.getName());
if(e.changeType == "OTHER") {
let tsh = e.source.getSheetByName("Sheet2");
sh.getRange(2,1,12,8).copyTo(tsh.getRange(2,1));//Only need the upper left hand corner of the range. Thus you change change the size of the source data without having to change the target range.
}
}
One might be inclined to attempt to use e.source.getActiveSheet() unfortunately this does not necessary get the sheet that you have written unless in is SpreadsheetApp.getActive().getSheets()[0]. onChange event object always returns the most left sheet in the spreadsheet in this situation so it can't be used to identify the sheet that is being written to from the importRange function.
With this function you no longer require another function.
A simple if should work, but you're trying to compare the name to the wrong field. According to Google's documentation, the source field in the Event Object e returns the Spreadsheet object, which is the entire document.
Instead you can use the e.range field, which contains the exact range where the edit was made, and the Range class has a getSheet() method to get the parent Sheet.
So you can modify your sample to the following:
function Trigger2(e) {
var sheet = e.range.getSheet();
if (sheet.getName() === 'Copy') {//Edit: this will only work if sheet is the most left sheet in the spreadsheet
copyInfo();
}
}
I was able to find script that work for each sheet in a separate file, but can I have 2 scripts applied to a single file, but 2 sheets respectively? Essentially what I am after is this:
I am a pilot and I keep track of my logbook and flight times in a Google Sheets file. I have used this means for years but recently tried to apply some scripts to get to the first empty cell in column A as that is where I start with the date for each entry. The main reason for this is my sheet is now in excess of 3000 rows of entries. The first sheet in my file where I record my flights is titled "LOGBOOK". I have found a script to apply onOpen that will place my cursor to the first empty row of column A each time I open. Below is the script that I use. It works but takes approx 10 seconds to complete. If there is a faster means, I am open to suggestions. I only want to know the first empty cell in column A. This is what I use currently:
function onOpen() {
var sheet = SpreadsheetApp.getActiveSheet();
var values = sheet.getRange("A:A").getValues();
var maxIndex = values.reduce(function(maxIndex, row, index) {
return row[0] === "" ? maxIndex : index;
}, 0);
sheet.setActiveRange(sheet.getRange(maxIndex + 2, 1));
}
Screen shot of Sheet 1, "LOGBOOK"
The second sheet in this file is titled "FLIGHT DUTY" In this sheet I want the same function to happen onOpen. I want the cursor to be placed in the first empty cell of column A as well. Again, I start column A with a date input for the given flight duty period. If I break this sheet out to a stand along Google Sheets file, this script works:
function onOpen(){
var sheet = SpreadsheetApp.getActiveSheet();
//Find the last cell with data in that specific column (A in this case)
var lastCell = sheet.getRange("A1").getNextDataCell(SpreadsheetApp.Direction.DOWN);
//Activate the next cell
lastCell.offset(1, 0).activate();
}
Screen shot of sheet 2 "Flight Duty"
In my current Google Sheets file, I want Sheet 1 "LOGBOOK" to onOpen, place my cursor to the first empty cell in Column A. At the same time, onOpen, I want Sheet 2 "FLIGHT DUTY" to place my cursor in the first empty cell of Column A of that sheet when I select that tab.
Below is a screen shot of the SHEET names at the bottom. The only sheets that I need a script for are 1 LOGBOOK and 2 FLIGHT DUTY.
Screen shot of Sheet Names
Here is a more stable version
function onOpen(){
var ui = SpreadsheetApp.getUi();
ui.createMenu('↓ Go to ... ↓')
.addItem('first empty row', 'goToFirstEmptyRow')
.addToUi();
var sh = event.source.getActiveSheet();
sh.getRange('A'+getFirstEmptyDataRow(sh)).offset(1,0).activate();
}
function goToFirstEmptyRow(event){
var sh = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
sh.getRange('A'+getFirstEmptyDataRow(sh)).offset(1,0).activate();
}
function getFirstEmptyDataRow(sheet) {
var range = sheet.getRange("A4");
return range.getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
}
You have a menu at the top right
I tested the below code. The last row will be activated in as many sheets as you have. Whichever sheet you open, you will always land at the bottom row. Takes one second to run.
function onOpen() {
var ss = SpreadsheetApp.getActive();
var tabs= ss.getSheets();
for (i=0; i<tabs.length;i++){
var lr = tabs[i].getLastRow();
tabs[i].getRange("A"+(lr+1)).activate();
}
}
Try ...
function onOpen(event){
var sh = event.source.getActiveSheet();
sh.getRange('A'+getFirstEmptyDataRow(sh)).offset(1,0).activate();
}
function onSelectionChange(event){
var sh = event.source.getActiveSheet();
sh.getRange('A'+getFirstEmptyDataRow(sh)).offset(1,0).activate();
}
function getFirstEmptyDataRow(sheet) {
var range = sheet.getRange("A4");
return range.getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
}
https://docs.google.com/spreadsheets/d/1Um9gnfz3VH6Vf0PwNVW8LOD3F63qb5h7PQl-hSYT_0E/copy
I've written an apps script in GSheet to:
Copy the last row of the Google Form sheet in Google Sheet file if a new entry arrived (trigger) by Google Form
Paste this row to another sheet in the same GSheet file to the last row
Unfortunately it copies the last row of the Form sheet more than one time to the appropriate sheet. Sometimes two times, sometimes four times. I cannot see my mistake in the code.
I couldn't find a solution so far. I included a pause before appendRow with Utilities.sleep(5000) but w/o effect.
function myFunction() {
// Source sheet: Form is the source sheet of the Google Form
var source =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form");
// Get last row in Form
var lastrow = source.getLastRow();
// Get just the date of the Form input
var formdate = source.getRange(lastrow,7,1,7).getValue();
// Change month number to string (e.g. April)
var currentD2 = Utilities.formatDate(formdate,
Session.getScriptTimeZone(), "MMMMM");
// Identify target sheet in same Google sheet file
var target =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName(currentD2);
// Identify the appropriate range of the last row of the Form sheet
// (source)
var sourceData = source.getRange(lastrow, 2,1,8).getValues();
// Append the last row of the Form sheet to the last row in the target
// sheet
target.appendRow(sourceData[0])
}
I expect that the function copies just one time the last row of the Form sheet and not multiple times.
Hello I guess that you are using an onEdit trigger to start your function.
That means that when you change the "Google Form sheet" it triggers your function which changes another sheet in the same spreadsheet which starts your trigger again.
I recommend to rename your function onEdit(e)
ref(https://developers.google.com/apps-script/guides/triggers/events)
and then condition your action with: if e.range belongs to your "google form sheet" then do something otherwise not.
function onEdit(e) {
if (e.range.getSheet().getName()='your google form sheet') {
// insert here the contents of your function
}
}
you can use either this :
function appendEntry(){
var ss = SpreadsheetApp.getActiveSpreadsheet()
var ws = ss.getSheetByName("data") // this is the sheet where new entry arrive
var ws2 = ss.getSheetByName("target") // this is the sheet where the entries will go
var data = ws.getDataRange().getValues()
var data2 = ws2.getDataRange().getValues()
for(var i=0;i<data.length;i++){
var row = data[i]
for(var k=0;k<data2.length;k++){
var row2 = data[k]
if(row2[0] != row[0]){
} // This will loop through both sheet
// If entry is already mentioned in other sheet the data will not append
}
}
ws2.appendRow(data[k]) //other wise entry will append to the next sheet
}
I have a spreadsheet-bound script that is invoked by clicking an image in the spreadsheet. I've found that the script can be blocked if a cell that it needs to modify is active or being edited by the user.
The code could be anything, even as something as simple as this:
function newf() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange("R4");
range.clear();
}
When the cell R4 is open, the function will ignore the cell.
How do I deselect an open cell when clicking on an image to run a script?
The easy (manual) solution would be to click off the open cell prior to clicking on the image, but I am trying to make the script fool-proof and this can be a costly concern. Is there any way that the function can forcefully deselect an active cell?
Edit: I do not mean active cell. Definitely "open cell." Here are pictures of the error.
Open cell with data being entered
Clicked on Picture, program ran
You can use Sheet.setActive() to move the attached user's cursor to a different location in the spreadsheet. You need to remember that there is a relationship between the User Interface and scripts invoked from it, which is what allows the script to determine and change the currently active cell. This does not extend to the multiple user case, though - if User1 is editing the guarded cell when User2 clicks to run the script, the script will have no idea about it.
function newf() {
var safePlace = "A1"; // Some safe cell
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var activeCell = ss.getActiveCell();
if (activeCell.getSheet() == sheet && activeCell.getA1Notation() == "R4")
sheet.setActiveSelection(safePlace);
var range = sheet.getRange("R4");
var activeCell = ss.getActiveCell();
if (activeCell.getA1Notation() == range.getA1Notation() && activeCell.getSheet().getName() == range.getSheet().getName()) {
// Move user from target cell
sheet.setActiveSelection(safePlace);
range.clear();
}
I had the same problems, both the initial one and with the solution proposed by Mogsdad.
I resolved it the following way:
Before running any of the script, the first three lines get a different sheet opened and the spreadsheet gets flushed:
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('DIFFERENT SHEET NAME'), true);
SpreadsheetApp.flush();
The rest of the script follows.
Then at the end of the script, I reopen the initial sheet with a simple:
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('ORIGINAL SHEET NAME'), true);
Another "solution" to this is not to use a button, but use a check mark as if it we a button, and use the onEdit function.
function onEdit(e)
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
checkbox1 = ss.getRange("saveCheckbox_001");
if (checkbox1.isChecked())
{
submit1(); //do all the fun stuff associated with this checkbox
checkbox1.uncheck();
}
//more checkbox handling and calling other functions can go here if you need more than one "button"
}
I am pretty new to google sheets script development and am wondering how to trigger a clearAll script with cell value i.e. A1=100.
My clearAll script works (see below), though I don't know what to add to it to trigger it using a specific cell value.
function clearAll() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formresponses1 = ss.getSheetByName("formresponses1");
formresponses1.clearContents();}
Thanks
If you are trying to make it so that whenever somebody puts in the value of "100", it clears the content of the entire sheet, then you can do this:
function onEdit(e) {
var ss = SpreadsheetApp.getActive() //gets the active spreadsheet
var sheet = SpreadsheetApp.getActiveSheet() //gets the active sheet
var cell = ss.getActiveRange() //gets the active cell
var cellContent = cell.getValue() //gets the value of the active cell
if(cellContent === 100) {
sheet.clearContents() //clears the values of the entire active sheet
}
}
If you want to make it so that whenever somebody edits the cell and makes its value "100", the code clears only that cell, then do this:
function onEdit(e) {
var ss = SpreadsheetApp.getActive()
var sheet = SpreadsheetApp.getActiveSheet()
var cell = ss.getActiveRange()
var cellContent = cell.getValue()
if(cellContent === 100) {
cell.setValue("") //clears the value of the active cell
}
}
Of course, in the latter, due to the slight lag in Google scripts, if somebody rapidly puts in 100 in every cell they can, then some of the cells with a value of "100" will stay there, but if the person is putting the values in like a normal person rather than a spammer, then this code will work.
Also, if you are trying to make it so that if the value of a certain cell (ie: A1) is equal to "100," the script clears the entire sheet, do this:
function onEdit(e) {
var ss = SpreadsheetApp.getActive()
var sheet = SpreadsheetApp.getActiveSheet()
var cell = sheet.getRange('A1')
var cellContent = cell.getValue()
if(cellContent === 100) {
sheet.clearContents()
}
}
Hope I could help!
The onEdit trigger runs when any cell in the spreadsheet is edited.
Google Documentation - Spreadsheet On Edit
There is also a change trigger. It is an installable trigger, not a simple trigger.
Available types of triggers
Quote from documentation:
An installable change trigger runs when a user modifies the structure
of a spreadsheet itself — for example, by adding a new sheet or
removing a column.
I think the only thing that will work for you is the On Edit trigger. The trigger gets set from the Resources menu in the Apps Script Code editor.
I don't think you can restrict the code from running only to a certain cell, or a certain value within a cell. The code will run every time you edit ANY cell.
I think that the only other alternative would be to run a time based trigger, and have the script get the value of that cell, and check the value. The shortest time interval you can use is to run a script every minute. So if you edited the cell on second 1, it would take 59 more seconds before anything happened.
If you had some type of user interface, and the value in that cell was written to when the user entered a value in an input field, you could detect that change immediately, and make something happen.