I am stuck with, at first sight, simple script.
I want to clear a content from cell S when T has value "Copied".
What I have at the moment is this:
function onEdit(e) {
if(e.range.columnStart === 20) {
e.range.offset(0,-1).clearContent();
}
}
I am not sure how to include IF. Also, bear in mind that T column has a formula, so I don't edit it manually, and with this script, it doesn't work.
It doesn't have to be OnEdit, I can set a trigger to run the script every minute which is even better, but it is important to filter it by the value Copied.
To explain a bit more how my file works (example):
1) I add a comment in the cell S5.
2) My second script runs every minute where it copies values from column S to column V.
3) In the column T, I have the formula (=IF(V5<>"",IF(RegExMatch(S5,V5),"Copied",""),"")), which means if the value exist in the column V5 add Copied in cell T5.
4) I am looking for a solution that when cell T:T has "Copied", delete the cell range S:S
Thank you millions!
As #TheWizEd points out the value in T is dependant on the result in another cell. However an OnEdit function does not necessarily have to respond to the range where the change was made. I've used this code to use the OnEdit event to evaluate the values in Column T and then make the relevant change to values in Column S.
Column T uses a for loop to go through the various row, but the relevant value is pushed to array. This allows a single setValues to be executed at the end of the function.
The function should be assigned to the OnEdit trigger for the Spreadsheet.
function so_53469142() {
// Setup spreadsheet and target sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("so_53469142");
// get the last row of data
var lastRow = sheet.getLastRow();
// get range for Column S & T
var values = sheet.getRange(1, 19, lastRow, 2).getValues();
// set counter variable
var i = 0;
var dataArray = [];
var masterArray = [];
// start loop
for (i = 0; i < lastRow; i++) {
// Logger.log("i="+i+", S = "+values[i][0]+", T = "+values[i][1]);//DEBUG
// empty the array
dataArray = [];
// test value of first row in T
if (values[i][1] === "Copied") {
// If value = "Copies then push blank onto array for Column S
dataArray.push("");
} else {
// else push existing value for column S
dataArray.push(values[i][0]);
}
// make the array 2D
masterArray.push(dataArray);
}
// Update values in Column S
sheet.getRange(1, 19, lastRow).setValues(masterArray);
}
Related
I'm trying to build an automated list with google sheets. The first sheet(A) is for input of production data of a week. The second sheet(B) should be the data archive. Thus i want the content from sheet A copied to sheet B and then deleted in sheet a. It should be copied in the next empty range in sheet B.
My problem must be inside the notation of the "while" or / and the "if" but nothing seems to work properly.
The while checks if sheet A is already emptied, if not the "if" function checks a specific range in sheet B if it is empty. If thats the case it should be copying the data and then delete it. Else the column of the range in sheet b is changed to the next range (spaltennummer + 6).
While troubleshooting it either stays in the while (finds no empty range?) or it runs through without any effect. I tried "== 0", "== """, isblank and so on. (every option available?). Google didnt seem able to provide me an answer...
Thanks for ur help.
Code:
function myFunction() {}
function leerzelle(){
var spaltennummer = 4;
var rangeDatenarchiv = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Tabellenblatt1").getRange(8,spaltennummer,15,5);
var rangeDateneingabe = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Dateneingabe").getRange("J8:N22");
var values = rangeDateneingabe.getValues();
while(rangeDateneingabe !== 0){
if(rangeDatenarchiv == 0) {
rangeDatenarchiv.setValues(values);
rangeDateneingabe.setValues("");
}
else{
spaltennummer = spaltennummer + 6;
}
}
}
Try this:
Code:
function leerzelle() {
var spaltennummer = 4;
// Data entry
var rangeDateneingabe = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Dateneingabe").getRange("J8:N22");
var values = rangeDateneingabe.getValues();
// While we have value in data entry sheet
while (!rangeDateneingabe.isBlank()) {
// Update archive everytime you increment in else
var rangeDatenarchiv = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Tabellenblatt1").getRange(8, spaltennummer, 15, 5);
// If archive range is blank, move values
if (rangeDatenarchiv.isBlank()) {
rangeDatenarchiv.setValues(values);
rangeDateneingabe.clearContent();
}
else {
spaltennummer = spaltennummer + 6;
}
}
}
Note:
You just can't compare a range to a number, use isBlank to check if it doesn't have values instead.
You can't use setValues("") on a range to remove the contents, use clearContent instead to delete those values.
You need to redeclare archive range everytime you loop using the incremented column number
References:
isBlank
clearContent
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'll start this off by saying I have no clue what I'm doing. I'm surviving off copying and pasting code off the internet for a spreadsheet me and my friends use for watching films together.
I've run into an issue where I'm updating a cell with the current date when another cell in that row is updated if its blank with a script.
This issue is I then use a function in the cell next to it to give the difference in days for another date marked down in a cell (like a normal spreadsheet as that easier for me to do). But every time the script runs the function breaks and is replaced with the text "#NUM!" (Actually has that text as the function disappears from inside it).
I tried changing it to =U2 and that breaks also. Is this something that can't be done? The great almighty google god has not provided me with an answer so I've made an account here in hope of salvation.
tl;dr Scrips look like they are breaking my cell references for any sheet function that looks at cells they edit. How stop?
In cell V2 I have the function =DATEDIF(S2,U2,"D")
Script bellow (I know not how to format)
function onEdit(event) {
var eventRange = event.range;
var sheetName = SpreadsheetApp.getActiveSheet().getSheetName();
if (sheetName == "Scores") {
if (eventRange.getColumn() == 10) { //Check which is updated
var columnXRange = SpreadsheetApp.getActive().getSheetByName("Scores").getRange(eventRange.getRow(), 21, eventRange.getNumRows(), 21);//where to write
var values = columnXRange.getValues();
for (var i = 0; i < values.length; i++) {
if (!values[i][0]) { // If cell isn't empty
values[i][0] = new Date();
}
}
columnXRange.setValues(values);
}
}
}
Ok, I see the problem. You are looking at a way bigger range than you want with
var columnXRange = SpreadsheetApp.getActive().getSheetByName("Scores").getRange(eventRange.getRow(), 21, eventRange.getNumRows(), 21);
You only really need the value of one cell to check if it is empty. Try replacing your function with :
function onEdit(event) {
var eventRange = event.range;//makes shit happen?
var sheetName = SpreadsheetApp.getActiveSheet().getSheetName();//checks current shit
if (sheetName == "Scores") {//name of sheet want shit to happen
if (eventRange.getColumn() == 10) { // 1 is column A, 2 is B ect
// getRange(row, column, numRows, numColumns) sheet name to make not everywhere
var columnXRange = SpreadsheetApp.getActive().getSheetByName("Scores").getRange(eventRange.getRow(), 21, 1, 1);//num is where will write 1 is a ect
var values = columnXRange.getValues();//takes all shit from above to use as range
if (!values[0][0]) { // If cell isn't empty
values[0][0] = new Date();//set date to the vaules in the range
}
columnXRange.setValues(values); //use the values set above and write them in
}
}
}
..and that should fix your problem. The problem with your current script is that the script is copying the "value" of your column v cells and replacing it with just a text value. This limits the range you are grabbing to just the cell you need, eliminates the for() loop, and steps over the problem entirely.
I have this script which is working well, but i need to edit it to
a) only return new rows since last run
b) only return certain cells instead of whole row
any guidance would be greatly appreciated
function Copy() {
var sourceSheet = SpreadsheetApp.openById('1WAtRDYhfVXcBKQoUxfTJORXwAqYvVG2Khl4GuJEYSIs')
.getSheetByName('Jobs Log');
var range = sourceSheet.getRange(1, 1, sourceSheet.getLastRow(), sourceSheet.getLastColumn());
var arr = [];
var rangeval = range.getValues()
.forEach(function (r, i, v) {
if (r[1] == 'Amber') arr.push(v[i]);
});
var destinationSheet = SpreadsheetApp.openById('137xdyV8LEh6GAhAwSx4GmRGusnjsHQ0VGlWbsDLXf2c')
.getSheetByName('Sheet1');
destinationSheet.getRange(destinationSheet.getLastRow() + 1, 1, arr.length, arr[0].length)
.setValues(arr);
}
In order to only check new data added after last runtime we have to store .getLastRow() value in properties and retrieve it every runtime. We would also have to work under a few assumptions:
In the input data new values are only appended at the bottom and never inserted between other data
Data is never deleted from the input sheet (if you ignore this, then you must also have an update script for the last row that runs after deleting data)
The sheet is not sorted after new data is added but before this script is run.
So you would need something along the lines of
var sourceSheet = SpreadsheetApp.openById('1WAtRDYhfVXcBKQoUxfTJORXwAqYvVG2Khl4GuJEYSIs')
.getSheetByName('Jobs Log');
var lastRow = sourceSheet.getLastRow();
// note that you need to hav the script property initialized and stored
// or adjust the if to also check if prevLastRow gets a value
var prevLastRow = PropertiesService.getScriptProperties().getProperty('lastRow')
if (lastRow <= prevLastRow) {
return; // we simply stop the execution right here if we don't have more data
}
// then we simply start the range from the previous last row
// and take the amount of rows added afterwards
var range = sourceSheet.getRange(prevLastRow,
1,
lastRow - prevLastRow,
sourceSheet.getLastColumn()
);
As for the second question, inside the forEach you need to simply push an array into arr that will contain only the columns you want. So for example
if (r[1] == 'Amber') arr.push(v[i]);
changes into
if (r[1] == 'Amber') arr.push([v[i][0], v[i][3], v[i][2]]);
which will output A D C columns (in that order) for each row.
Finally, the last thing you need to run before the script ends is
PropertiesService.getScriptProperties().setProperty('lastRow', lastRow)
which will let us know where we stopped the next time we run the script. Again, keep in mind that this works only if new data will always be in new rows. Otherwise, you need to do a different method and retrieve 2 arrays of data. 1 for the entire input sheet and 1 for the output sheet. Then you would have to perform 2 if checks. First one to see if your criteria are met and a second one to see if it already exists in the output data.
I've been searching for quite a while so hopefully no one else has asked this.
I have a Google Spreadsheet with two sheets, one a sort of database containing form submissions and the other a way for users to interact with submissions one at a time.
Basically I want users to be able to make changes to a submission and save them back to the same line that they came from in the original sheet.
I have the code to send the changes back but I can't figure out how to get the coordinates of the correct row:
function saveChanges() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheets()[0];
var destination = ss.getSheets()[1];
var range = source.getRange("A40:BL40");
// Find coordinates of the row where value of cell A40 matches a cell in A:A in second spreadsheet
// This copies the data in A40:BL40 in the source sheet to
// D4:F6 in the second sheet
range.copyValuesToRange(destination, 1, 64, 16, 16);
}
At the moment the data is just written to the coordinates "1, 64, 16, 16" which just points to a currently empty row - ideally I'd change that to a variable with the right coordinates.
The value of cell A40 is a unique ID and ideal for searching the second sheet but I can't figure out how.
I'm very new to Javascript so any help would be greatly appreciated.
To find your matching value in the form response sheet, you must loop through the range to find a match. There are a number of ways to do that, I'll show a couple.
Here's a version of your saveChanges() function that will get all the data from your destination sheet, look through it's column A for a match to the value in A40, then update the data in that row.
function saveChanges() {
var uniqueIdColIndex = 0; // Col "A" has unique ID, is element 0 in row array
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheets()[0];
var destination = ss.getSheets()[1];
var sourceData = source.getRange("A40:BL40").getValues();
var destData = destination.getDataRange().getValues();
// Find coordinates of the row where value of cell A40 matches a cell in A:A in second spreadsheet
for (var rowIndex=0; rowIndex < destData.length; rowIndex++) {
if (sourceData[0][uniqueIdColIndex] == destData[rowIndex][uniqueIdColIndex]) {
// Found our match
destination.getRange(rowIndex+1,1,sourceData.length,sourceData[0].length)
.setValues(sourceData);
break; // Done, exit loop
}
}
}
Here's another way to do it. This time, we don't read all the data in the destination sheet, only the info in column A. To be able to take advantage of array lookup methods, the two-dimensional array retrieved via .getValues() needs to be transposed first - so we use a helper function to do that. (I'm using the transpose() function from this answer.)
function saveChanges() {
var uniqueIdColIndex = 0; // Col "A" has unique ID, is element 0 in row array
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheets()[0];
var destination = ss.getSheets()[1];
var sourceData = source.getRange("A40:BL40").getValues();
// Get column A from destination sheet
var destDataTrans = transpose(destination.getRange(1, 1, destination.getLastRow(),1).getValues());
// Find coordinates of the row where value of cell A40 matches a cell in A:A in second spreadsheet
var destRow = destDataTrans[0].indexOf(sourceData[0]) + 1; // +1 to adjust to spreadsheet rows
if (destRow > 0) {
// Found our match
destination.getRange(destRow,1,sourceData.length,sourceData[0].length)
.setValues(sourceData);
}
}
The second approach has fewer lines of code, but should be a bit slower than the first one because of the transpose() function which touches every element in column A before performing a search with .indexOf(). (The first approach searched in place, and exited once a match was found, so it actually does less work.)
In both examples, I've tried to limit the calls to google's services as much as possible. Alternatively, you could read info from the spreadsheets inside the search loop, which would be much slower, but would avoid the +1 / -1 mental gymnastics needed to keep 0-based arrays aligned with 1-based rows and columns.