Google sheet script to update cell value in another sheet - google-apps-script

I'm trying to use a code I found in another topic but so far I failed. I'm trying to find the corresponding row and update the last column in another google spreadsheet after updating the first column of another spreadsheet.
When the user selects "ready" in ColC of spreadsheet X, I need to look up the ID value in ColB on another sheet (Y). Then I need to access spreadsheet Y and find the row that contains that same ID. Access the last column or columnC (3) and change the cell value to "ready".
Here is what I have so far but I get the error:
TypeError: Cannot read properties of undefined (reading 'range')
Can anyone help me fix this?
function onEdit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
if( s.getName() == "Studio"){
if ( (e.range.getColumn() == 2.0) && (e.range.getValue() == "ready") ){
var nextCell = e.range.offset(0, -1);
var buybackId = nextCell.getValue();
var ss2 = SpreadsheetApp.openById('1bbNuH_GXbmNwMHk7Kjo0zAhD-QGwQqiva8_HJc3mgFY');
var sheet = ss2.getSheetByName("Status");
var data = sheet.getDataRange().getValues();
for(var i = 0; i<data.length;i++){
if(data[i][1] == buybackId){
sheet.getRange((i+1), 3).setValue("ready");
}
}
}
}
}
The table structure looks like this:

In the 1st section of your question, you say I'm trying to find the corresponding row and update the last column in another google spreadsheet after updating the first column of another spreadsheet.. But, from your 2nd section, it seems that you wanted to run the script by editing column "B" like ready. By the way, about When the user selects "ready" in ColC of spreadsheet X,, your selects is to edit?
So, when column "B" in "Studio" sheet on the active Spreadsheet is edited to like ready, you want to update column "C" of "Status" sheet in another Spreadsheet by checking the ID of column "B".
If my understanding is correct, how about the following modification?
Modification points:
In order to use SpreadsheetApp.openById, it is required to use the installable OnEdit trigger.
About your current error of TypeError: Cannot read properties of undefined (reading 'range'), I'm worried that you might have directly run the function onEdit with the script editor. In that case, such an error occurs because of no event object.
When these points are reflected in your script, how about the following modification?
Modified script:
Please copy and paste the following script to the script editor of Spreadsheet. And, please install OnEdit trigger to installedOnEdit. When you use this script, please edit the column "B" of "Studio" sheet of the active Spreadsheet. By this, the script is run. And, please set your destination Spreadsheet ID to const ss = SpreadsheetApp.openById('###');.
function installedOnEdit(e) {
const { range } = e;
const sheet = range.getSheet();
if (sheet.getSheetName() != "Studio" || range.columnStart != 2) return;
const [id, status] = range.offset(0, -1, 1, 2).getDisplayValues()[0];
const ss = SpreadsheetApp.openById('###'); // Please set the destination Spreasheet ID.
const dstSheet = ss.getSheetByName("Status");
const r = dstSheet.getRange("B2:B" + dstSheet.getLastRow()).createTextFinder(id).matchEntireCell(true).findNext();
if (!r) return;
r.offset(0, 1).setValue(status);
}
When this script is run by the installable OnEdit trigger, the above goal can be achieved.
Note:
If you want to copy all values from the source sheet to the destination sheet, how about the following sample script? In this case, you can directly run the script with the script editor.
function sample() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Studio");
const obj = sheet.getRange("A2:B" + sheet.getLastRow()).getDisplayValues().reduce((o, [a, b]) => (o[a] = b, o), {});
const ss = SpreadsheetApp.openById('###'); // Please set the destination Spreasheet ID.
const dstSheet = ss.getSheetByName("Status");
const dstRange = dstSheet.getRange("B2:C" + dstSheet.getLastRow());
const dstValues = dstRange.getValues().map(([b, c]) => [obj[b] || c]);
dstRange.offset(0, 1, dstValues.length, 1).setValues(dstValues);
}
Note:
About the 1st script, when you directly run the script, an error like TypeError: Cannot destructure property 'range' of 'e' as it is undefined. occurs. Please be careful about this. The 1st script is automatically run with the installable OnEdit trigger.
By the way, when you use the 1st script, please rename your onEdit function name. Because when you didn't rename it, when the cell is edited, both functions installedOnEdit and onEdit are run. Please be careful about this.
Reference:
Installable Triggers

Related

Moving one row to bottom of a sheet after date has passed

I am looking for a macro that can help move a row of a sheet to the bottom of it once it passes a certain date. Basically this will be used for a meeting tracker and I'm trying to find a way to automatically move meetings to a "Completed" section once the date (located on Column F) passes.
I've created macros before to move things between sheets, but I'm unfamiliar with how to move things on the same sheet. Would anyone be able to help?
Here's the sheet: https://docs.google.com/spreadsheets/d/1EPueop9bdky_J8VgpFdSUzzsMRieRUreeCRIy18ScTY/edit#gid=0
I would like to move rows based on the date in Column F. Once it passes I would like it to move to the "Completed" section of the sheet. This is an active spreadsheet so the row "Completed" it's on could change as meetings are being added.
function moveActiveRowToBottom() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const r = sh.getActiveRange().getRow();
const vs = sh.getRange(r,1,1,sh.getLastColumn()).getValues();
sh.getRange(sh.getLastRow() + 1, 1, vs.length, vs[0].length).setValues(vs);
sh.deleteRow(r);
}
I believe your goal is as follows.
You want to check the date of column "F" of the sheet. When the date of column "F" is smaller than today, you want to move the row to the last row.
You want to achieve this in the same sheet in a Google Spreadsheet. And, the sheet has a row of "Completed" in column "A", you want to check the date of the above rows of the "Completed" row.
In this case, how about the following sample script?
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet, and save the script. When you use this script, please run the function of myFunction().
function myFunction() {
const sheet = SpreadsheetApp.getActiveSheet(); // or const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet 1");
const row = sheet.getRange("A4:A" + sheet.getLastRow()).createTextFinder("Completed").findNext().getRow();
const now = new Date().getTime();
const moves = sheet.getRange("F4:F" + (row - 1)).getValues().reduce((ar, [f], i) => {
if (f && f.getTime() < now) {
const r = i + 4;
ar.push(sheet.getRange(`A${r}:F${r}`));
}
return ar;
}, []).reverse();
const len = moves.length;
if (len == 0) return;
const lastRow = sheet.getLastRow();
moves.forEach((r, i) => sheet.moveRows(r, lastRow - i + len - 1));
}
When this script is run, the column "F" of the rows from 4 to the "Completed" row is checked. And, when there are moving rows, the rows are moved to the last row of the sheet.
Note:
This sample script was tested using your provided Spreadsheet. When you change the Spreadsheet and/or your actual Spreadsheet is different from your provided Spreadsheet, this script might not be able to be used. Please be careful about this.
When I saw your sample Spreadsheet, it seems that the sheet name is Sheet 1. If you want to use the sheet using the sheet name, please be careful about this.
References:
reduce()
forEach()
moveRows(rowSpec, destinationIndex)

Vlookup with split text by Google Appscript

I am new to Appscript hence any help on below will be really appreciated.
My query is similar to the one posted in the below link,however, in that question the job is done by custom function and it is working bit slow and runs on every edit. In place of custom function I want to design an Appscript for the same which runs on the change of dropdown.
Link to similar question:
Google Appscript partial vlookup
Link of sample spreadsheet.
https://docs.google.com/spreadsheets/d/1vI22QCmixKe3aoWMLODTFzt7pNXIKO3pjXS4mT6GHT0/edit#gid=0
Any help on above will really be appreciated.
I believe your goal is as follows.
You want to run the script when the dropdown list of cell "A1" of "Sheet3" is changed to "Refresh".
You want to obtain the same result with your following script.
function MYLOOKUP(data1, data2) {
return data1
.map(([rollNo_Name, value]) => {
return (rollNo_Name !== '' && value === '') ?
data2.find(([rollNo,]) => rollNo_Name.split('*')[0] == rollNo)[1] :
''
});
}
In this case, how about using the OnEdit trigger of the simple trigger? When this is reflected in your sample Spreadsheet, the sample script is as follows.
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet and save the script. When you run the script, please change the dropdown list of cell "A1" of "Sheet3" to "Refresh". By this, the script is run.
function onEdit(e) {
const sheetName = "Sheet3"; // This sheet name is from your Spreadsheet.
const { range, value, source } = e;
const sheet = range.getSheet();
if (sheet.getSheetName() != sheetName || range.getA1Notation() != "A1" || value != "Refresh") return;
const sheet1 = source.getSheetByName("Sheet1"); // This sheet name is from your Spreadsheet.
const sheet2 = source.getSheetByName("Sheet2"); // This sheet name is from your Spreadsheet.
const range1 = sheet1.getRange("A2:B" + sheet1.getLastRow());
const obj = sheet2.getRange("A2:B" + sheet2.getLastRow()).getValues().reduce((o, [a, b]) => (o[a] = b, o), {});
const values = range1.getValues().map(([a, b]) => {
const temp = obj[a.split("*")[0]];
return [temp && !b.toString() ? temp : null];
});
range1.offset(0, 2, values.length, 1).setValues(values);
range.setValue(null);
}
In this script, when the dropdown list of cell "A1" of "Sheet3" is changed to "Refresh", the script is run. And, the same result with your script is obtained. And, the value of the dropdown list is changed to null.
The result values are put to column "C" of "Sheet1". If you want to change this, please modify the above script.
Note:
In this script, when you directly run the function onEdit with the script editor, an error occurs. Please be careful about this.
In this script, in order to search the values, I used an object. By this, the process cost might be able to be reduced a little.
Updated: I reflected value in to be pulled only when there is no value in Column B.
References:
Simple Triggers
reduce()
map()

How can I get a different cell to activate depending on the contents of an edit?

In Google Apps Script for Google Sheets, I want to use a barcode scanner to input new data into a cell. If this new data matches what I am expecting, I want another cell to then be activated so I can continue to input data. This is being used to create an inventory, and the initial data being input (and what is being checked by the code) will be the name of the person using the scanner at that time. Here's what I have so far:
var ss = SpreadsheetApp.getActive()
var sheet1 = SpreadsheetApp.getActive().getSheetByName("Sheet1")
//Set active cell to A2 (cell below Name header) when file is opened.
function onOpen() {
sheet1.getRange('A2').activate();
}
function onEdit(e) {
if (e.range.getA1Notation() === 'A2') {
var nameval = sheet1.getActiveCell.getValue();
if (nameval == 'Alpha' || nameval == 'Beta') {
sheet1.getRange('D7').activate();
}
else {
sheet1.getRange('F1').activate();
}}}
Unfortunately this does not seem to do anything - The value in the cell is accepted but the new cell did not activate when either of the activation cases were input.
Modification points:
In your script, sheet1 is not declaread.
getActiveCell of sheet1.getActiveCell.getValue() is not run as the method.
I thought that nameval == 'Alpha' || nameval == 'Beta' might be written by includes.
When these points are reflected in your script, it becomes as follows.
Modified script:
function onEdit(e) {
var range = e.range;
var sheet1 = range.getSheet();
if (range.getA1Notation() === 'A2') {
var active = ['Alpha', 'Beta'].includes(range.getValue()) ? "D7" : "F1";
sheet1.getRange(active).activate();
}
}
In this modified script, when a value of 'Alpha' or 'Beta' is put to the cell "A2", the cell "D7" is activated. When a value except for 'Alpha' and 'Beta' is put to the cell "A2", the cell "F1" is activated.
Note:
This script is automatically run by the OnEdit simple trigger. So, when you directly run this script with the script editor, an error occurs. Please be careful about this.
References:
Simple Triggers
includes()

How to use copyTo() without showing "destination" tab/sheet, keeping the user where he is (using Google Apps Script)?

there!
The functions below basically copies rows into another sheet. This is a tweaked sample of the code.
The issue I'm finding is:
When this copies the row(s) into the new sheet, that sheet becomes active. I'll delete it later, but the user gets dragged into this temporary sheet and I'd lie to know if there'd be a way to do it using copyTo() but without shifting tabs in front of the user.
/*
*It moves the order row into Production Sheet in WIP, as the user sets status Confirmed;
*/
function installedOnEdit(e) {
const dstSpreadsheetId = "XXXXXxxxxxxxxXXXXXXXXXX" // Please set the destination Spreadsheet ID.
const destSheetName = 'Página23'
//Maps the values to serve as criteria to decide if this should continue running
const ss = SpreadsheetApp.getActiveSpreadsheet();
const orderSheet = ss.getSheetByName('Orders');
const activeSheetName = ss.getActiveSheet().getName();
const thisRow = e.range.getRow();
const thisCol = e.range.getColumn();
const cellValue = e.range.getValue();
if (activeSheetName == orderSheet.getName() && thisRow > 5 && thisCol < 13 && cellValue == 'Confirmed') {
moveOrder(ss, orderSheet, thisRow, dstSpreadsheetId, destSheetName)
orderSheet.getRange(thisRow, thisCol).setValue('')
}
}
}
function moveOrder(ss, orderSheet, thisRow, dstSpreadsheetId, destSheetName) {
const orderHeaders = orderSheet.getRange(5, 1, 1, 13);//Gets the table headers.
const rowValues = orderSheet.getRange(thisRow, 1, 1, 13);//Gets the changed row
const newSheet = ss.insertSheet('MoveOrderToWIP');//Creates a temporary sheet
orderHeaders.copyTo(newSheet.getRange(newSheet.getLastRow() + 1, 1), { contentsOnly: true });//Moves the headers into the temporary sheet
rowValues.copyTo(newSheet.getRange(newSheet.getLastRow() + 1, 1), { contentsOnly: true });//Moves the row into the temporary sheet
}
Thank you!
When I saw your updated script, it seems that dstSpreadsheetId and destSheetName are not used in moveOrder function. Although I cannot understand whether you wanted to use only one same Spreadsheet, for example, when you want to keep the edited range as the active range even when the new sheet is inserted by insertSheet, how about the following modification?
Issue and solution:
In your script, ss is const ss = SpreadsheetApp.getActiveSpreadsheet();. In this case, when insertSheet is used, the activated ranges are changed. In this case, even when ss is used as e.source, the same result occurs. It seems that this is the current specification.
When you want to keep the active range even when insertSheet is used, please use SpreadsheetApp.openById() instead of SpreadsheetApp.getActiveSpreadsheet(). By this, the active range is not changed even when insertSheet is used.
When this is reflected in your script, it becomes as follows. In this modification, your showing script is modified. Please be careful about this.
Modified script:
From:
const newSheet = ss.insertSheet('MoveOrderToWIP');
To:
const newSheet = SpreadsheetApp.openById(ss.getId()).insertSheet('MoveOrderToWIP');
By calling the Spreadsheet from outside, even when insertSheet is used, the active range can be kept. In this case, even when Sheets API is used, the same result occurs.
Note:
In your script, after "MoveOrderToWIP" sheet was inserted, the script is run again, an error occurs. Because the same sheet name is existing in the Spredsheet. So, please be careful this.
Reference:
openById(id)
All you need is from range and to range there is no need to actually activate the either of the sheets
fromRange.copyTo(toRange);
Probably the insertSheet() is making it the active sheet. So make some other the sheet the active sheet after doing the insert

Protect row according to the data in cell (Yes/No)

I have a sheet with a table. When me or one of my editors changes the data of the cell in column F (so that it is not blank), I need the row to become protected for everybody except me (owner).
I have seen several similar questions here, but no one gives a working script... I will appreciate any help.
I believe your current situation and your goal as follows.
Your Spreadsheet is shared with some users.
When the user is edited the dropdown list of the column "F", when the value is not empty, you want to protect the row of columns "A" to "F".
After the row was protected, you want to edit by only the owner.
You want to achieve this using Google Apps Script.
In this case, I would like to propose to run the script with the installable OnEdit trigger. The sample script is as follows.
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet and please install the OnEdit trigger to the function of myFunction.
function myFunction(e) {
const sheetName = "Sheet1"; // Please set your sheet name.
const range = e.range;
const sheet = range.getSheet();
const value = range.getValue();
const row = range.getRow();
if (sheet.getSheetName() != sheetName || range.getColumn() != 6 || row == 1 || value == "") return;
const p = sheet.getRange(`A${row}:F${row}`).protect();
const owner = Session.getActiveUser().getEmail();
p.getEditors().forEach(f => {
const email = f.getEmail();
if (email != owner) p.removeEditor(email);
});
}
In this script, it supposes that the dropdown list is put to the cells "F2:F" from your sample image. If you want to change the range, please modify above script.
When you use this script, by an user who is not owner, please edit the dropdown list of the column "F" of "Sheet1". By this, when the value of dropdown list is not empty, the script works. And, the row is protected. The editor is only the owner.
References:
Installable Triggers
protect() of Class Range
Class Protection
Added:
About your following 2nd question in comment,
thank you very much!! It works, and I know now something about triggers - a new thing for me. Is it possible to extend this function for 3 sheets with the same structure (within one spreadsheet)? Sheet1, Sheet2,Sheet 3...
When you want to use above script for the specific sheets like "Sheet1", "Sheet2", "Sheet3", how about the following sample script?
Sample script:
function myFunction(e) {
const sheetNames = ["Sheet1", "Sheet2", "Sheet3"]; // Please set the sheet names you want to run the script.
const range = e.range;
const sheet = range.getSheet();
const value = range.getValue();
const row = range.getRow();
if (!sheetNames.includes(sheet.getSheetName()) || range.getColumn() != 6 || row == 1 || value == "") return;
const p = sheet.getRange(`A${row}:F${row}`).protect();
const owner = Session.getActiveUser().getEmail();
p.getEditors().forEach(f => {
const email = f.getEmail();
if (email != owner) p.removeEditor(email);
});
}