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
Related
I have searched high and low but I have been unable to find an answer (I am sure I am not explaining it right)
I have a Google Sheet that have multiple sheets (tabs) labeled TabA, TabB and TabC.
On this Google Sheet, I submit a slash command on Slack, which then auto-fills a row on one of the tabs using apps script.
What I am trying to do is simply insert a word called TabA into a specific cell each time a new row has been detected. And insert a word called TabB when a new row has been made on TabB sheet etc.
I am sure I just am typing my questions wrong which is why I am unable to find an answer.
I am not actually sure which part of the code posts to the sheet, I think it is this?
if(sheetName) {
sheetName = sheetName.charAt(0).toUpperCase() + sheetName.slice(1)
} else {
sheetName = "Current"
}
// Find sheet
var sheetFlag = false;
for (var r = 1; r < settings.length; r++) {
if (settings[r][1] == channelID) {
var sheetID = settings[r][2];
var targetChannelID = settings[r][4];
var title = settings[r][0];
sheetFlag = true;
break;
}
}
if (sheetFlag) {
var sheet = SpreadsheetApp.openById(sheetID).getSheetByName(sheetName);
if(!sheet) {
sheet = SpreadsheetApp.openById(sheetID).insertSheet(sheetName);
}
var lastRow = sheet.getLastRow();
var slackDetails = ["", "", text1, "","","","","",realName, new Date(),title,text2];
// paste the slack details to the sheet
sheet.getRange(lastRow + 1,1,1,slackDetails.length).setValues([slackDetails]);```
Thank you in advance
If I understood you correctly, you want to:
Keep track of new rows that are added to each sheet in your spreadsheet (TabA, TabB, TabC).
Write the name of the sheet in successive rows of column D of each sheet every time news rows are detected.
As you were told in the comments, Apps Script has no triggers to track changes made to the spreadsheet by a script. For example, onEdit trigger "runs automatically when a user changes the value of any cell in a spreadsheet".
Workaround (time-based trigger and script properties):
A possible workaround to this limitation is using a time-based trigger that will fire a function periodically. You can create this trigger manually, or programmatically, by running this function once:
function createTrigger() {
ScriptApp.newTrigger("trackRows")
.timeBased()
.everyMinutes(1)
.create();
}
This will fire the function trackRows every minute. This function's purpose is to track changes to each sheet rows since last time it was fired (in this example, 1 minute ago) and write the sheet name to a certain cell if the sheet has more rows with content than during last execution.
To accomplish this, you can use the Properties Service, which lets you store key-value pairs and use them in later executions.
The function trackRows could be something along the following lines:
function trackRows() {
var props = PropertiesService.getScriptProperties();
var ss = SpreadsheetApp.openById("your-spreadsheet-id"); // Please change accordingly
var sheets = ss.getSheets();
sheets.forEach(function(sheet) {
var sheetName = sheet.getName();
var currentRows = sheet.getLastRow();
var oldRows = props.getProperty(sheetName);
if (currentRows > oldRows) {
var firstRow = 2;
var column = 4;
var numRows = sheet.getLastRow() - firstRow + 1;
var rowIndex = sheet.getRange(firstRow, column, numRows).getValues().filter(function(value) {
return value[0] !== "";
}).length;
var cell = sheet.getRange(rowIndex + firstRow, column);
cell.setValue(sheetName);
}
props.setProperty(sheetName, currentRows);
});
}
This function does the following:
Retrieve the script properties that were stored in previous executions.
Get all the sheets in the spreadsheet.
Check the last row with content in each sheet (via Sheet.getLastRow()), and compare the value with the one previously stored in script properties.
If the current last row is higher than the one stored in properties, write the sheet name in the first empty row of column D of the corresponding (starting at D2).
Store the current last row in script properties.
Notes:
The script is adding the sheet name to the first empty row of column D once, if it detects that new rows were added. It's not taking into account how many rows were added since last execution, it only considers if rows were added. This could easily be changed though, if that's what you wanted.
If you want to start from fresh, it would be useful to delete all previously stored properties. To do that, you could run this function once:
function deleteProps() {
var props = PropertiesService.getScriptProperties();
props.deleteAllProperties();
}
Reference:
Class ClockTriggerBuilder
Class PropertiesService
Sheet.getLastRow()
I manage a Google spreadsheet that has multiple sheets. I use scripts to automate some processes on the first sheet. Recently, a user accidentally renamed the first sheet -so the script stopped working until I found out and corrected the name of the sheet.
My question is: how can I protect against this happening again? Is there a way to protect the sheet name from being changed by users other than myself? Or, in the script, is there a different way to reference the first sheet, regardless of its name? I.e., instead of sheet.getSheetName is there something like sheet.getFirst that returns the first sheet, regardless of its name?
Here's a sample of one script that is being affected.
function onOpen(){
//Sheet where this script should run
var SHEETNAME = "Scan IN Check OUT"
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
if (sheet.getSheetName() == SHEETNAME ) { //Check we are on the correct sheet
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();
}
}
SpreadsheetApp.getActive().getSheets()[0]; will always be the first sheet on the left which may not always be the same sheet since users can change their position. You can use the first function below to see how the indexes change as you move sheets around. WARNING: don't do this in a spreadsheet that contains important information because it first clears the sheet and then writes the name, the id's and the indexes for all sheets.
So you can also use the first function to get the id of the sheet that you want not to change.
The second function will get the sheet by id. If you will select the id of your sheet and replace the string 'My Sheet ID' with it and also put the correct name of your sheet where it says my sheet name, then when you runTwo() it will move you sheets all the way to left (ie index 1) and rename to whatever you want.
function runOne() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var sht=ss.getSheets();
sh.clear();
sh.appendRow(['Name','id','index']);
sht.forEach(function(s){
sh.appendRow([s.getName(),s.getSheetId(),s.getIndex()]);
});
}
function getSheetById(id) {
var id=id||'My Sheet ID';
var ss=SpreadsheetApp.getActive();
var shts=ss.getSheets();
for(var i=0;i<shts.length;i++) {
if(shts[i].getSheetId()==id) {
return shts[i];
}
}
}
function runTwo() {
var ss=SpreadsheetApp.getActive();
var sh=getSheetById().activate();
ss.moveActiveSheet(1);
sh.setName('My Sheet Name');
}
Of course the real problem here is that if the user can change the name of your sheet they can also go in and change your script.
I have the following code that I took from this question. I am trying to get my 400+ line Google sheet to auto-scroll to the bottom of the sheet on open instead of the first row. I am getting an error for the code on line 2.
TypeError: Cannot read property "source" from undefined. (line 2, file "Code")
I have a feeling it is because I have more than one tab on my spreadsheet, but I do not know code well enough to fix it. I would like it to only work on the first tab.
function onOpen(e) {
var spreadsheet = e.source;
var sheet = spreadsheet.getActiveSheet();
var lastRow = spreadsheet.getLastRow();
/* if (sheet.getMaxRows() == lastRow) {
sheet.appendRow([""]);
}
lastRow = lastRow + 1;
*/
var range = sheet.getRange("A" + lastRow + ":A" + lastRow);
sheet.setActiveRange(range);
}
The instructions that Vidar S. Ramdal gave in the referred topic and which #OlegValter has tried to explain in the comments are VERY important. Be sure to do them all.
Go to Tools → Script editor and paste the following:
function onOpen(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetname = ss.getSheets()[0].getName();
// Logger.log("DEBUG: sheetname = "+sheetname)
var sheet = ss.getSheetByName(sheetname);
var lastRow = sheet.getLastRow();
var range = sheet.getRange(lastRow,1);
sheet.setActiveRange(range);
}
Delete any existing code that is called onOpen(), onOpen(e) or similar.
Click the Save button, then close the script editor, and the spreadsheet.
Now, open your spreadsheet again. Give it a couple of seconds, and you should see the cursor drop to the Column A on last row of content on the first sheet. This will happen regardless of what sheet/row/column was active when you last used the spreadsheet.
Note:
onOpen() is not a function that can be run from the Script Editor as #OlegValter has explained. If you click "run" to execute the function, it will NOT work. It will ONLY execute when the spreadsheet is opened.
Explanation
ss.getSheets()[0].getName();
getSheets() (Doc ref) gets all the sheets in the spreadsheet, and getSheets()[0] limits this to just the first sheet in the spreadsheet. getName() returns the name of the sheet. Combined, this line returns the name of the first sheet in the Spreadsheet.
ss.getSheetByName(sheetname);
getSheetByName(name)(Doc ref) returns a sheet with the given name.
sheet.getLastRow() -
returns the position of the last row that has content (Doc ref)
sheet.getRange(lastRow,1);
getRange(row, column) returns the range with the top left cell at the given coordinates. We use lastRow as the value of the row, and 1 to indicate Column A (Doc ref)
setActiveRange(range)
Sets the specified range as the active range in the active sheet, with the top left cell in the range as the current cell (Doc ref). In other words, the screen moves to the active cell.
I want to be able to quickly and easily add notes (Insert menu / Note) on multiple Google Sheets cells using cell values on other columns on that sheet. For example, F3 would grab the cell values in X3, F4 from X4. But also, if I grab G3, it would grab the values in Y3 (so it consistently grabs the values on the same row, but always the same number of columns to the right (in this case, always 18 columns to the right). I want to grab those values and stick them into a note on the left (so, columns F, G, etc.). I would like to be able to highlight a range (F3-F10), run the Google script, and it will grab all values on X3-X10 and stick them on their relative rows. Is this possible?
I've been hunting for this option, searching for every range of terms I can think of, and I've got pretty close, but I keep getting stuck at numerous points, the most important being able to grab the data from an equal number of columns to the right, relative to the range I highlight. I found this code that gets me close, but I don't know how to edit it to do what I need. It allows me to create the menu option, which works on pulling the value of A1 on the first tab of my spreadsheet (I'm working on the second tab, and it's only grabbing the same data for all of my notes). Here is the code I've been working on, but also a second function that I think has features that I need, such as grabbing the active sheet.
function noteSetter() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0]; //I need to make this grab the right sheet
var cell = sheet.getRange(2, 2); //this and the line below it seem to be critical here...
cell.setNote(sheet.getRange(1,1).getValue());
}
//below gives me the menu option to run the script, but I'm open to changing this
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Set cell note",
functionName : "noteSetter"
}];
sheet.addMenu("Scripts", entries);
};
Here is another one that is used on that post that has features that I think I need, but still doesn't get me the relative reference of the cells.
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSheet();
if (sheet.getName() == "Project"){ //Change to your sheet name
var sourceRange = e.source.getActiveRange();
var sourceRow = sourceRange.getRow();
var sourceColumn = sourceRange.getColumn();
var sourceValue = sourceRange.getValue();
if(sourceColumn == 1){ //Change to the column you want to get the value from
var noteCell = sheet.getRange(sourceRow, 2); //The cell where the Note should be, B-column on the current row
noteCell.setNote(sourceValue);
}
}
}
This is where I am now, but I may have messed some things up, as it no longer works at all.
function noteSetter() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var cell = sheet.getActiveRange();
cell.setNote(sheet.getRange(sourceRow, sourceColumn).getValue());
}
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Set cell note",
functionName : "noteSetter"
}];
sheet.addMenu("Scripts", entries);
};
Any suggestions on how to make this work?
Just to close the loop on this a little, I'm 90% of the way there with the below. It works perfectly when I highlight one cell. But if I highlight more (e.g. a range or array), it sticks the only the top and left-most value as the note in all cells I've highlighted.
function noteSetter() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
var row = sheet.getActiveRange().getRow();
var column = sheet.getActiveRange().offset(0, 18);
var cells = sheet.getActiveRange();
cells.setNote((row, column).getValue());
}
I think the next step is using the Selection class, but it's good enough for my purposes.
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
}