Searching a sheet for value stored in a cell fails - google-apps-script

I'm an extreme novice in programming and am trying to work an assignment that keeps failing. My code searches a sheet(tab1) in Google sheets for a value that is in a cell in another sheet(tab2). For some reason, the search never finds the value. For simplicity I tested by changing the value that I'm looking for to a whole number, not a ".GetValue". I then had an if statement that would alert with message "Found" and "Not Found" if it wasnt found. The data is never found.
function editRecord(){
var myGoogleSheet=SpreadsheetApp.getActiveSpreadsheet();
var shUserForm=myGoogleSheet.getSheetByName("Form");
var datasheet=myGoogleSheet.getSheetByName("Records");
var ui=SpreadsheetApp.getUi();
var response=ui.alert("Submit", 'Do you want to submit your data?', ui.ButtonSet.YES_NO);
var str=shUserForm.getRange("F7").getValue();
var values=datasheet.getDataRange().getValues();
var valuesFound=false;
if(response==ui.Button.NO){
return;
}
for (var i=0;i<values.length;i++){
if (values[i][1]==str){
var iRow=i+1;
datasheet.getDataRange(iRow,5).setValue(shUserForm.getDataRange("F15").getValue());
datasheet.getDataRange(iRow,6).setValue(shUserForm.getDataRange("F17").getValue());
datasheet.getDataRange(iRow,7).setValue(shUserForm.getDataRange("F19").getValue());
ui.alert("Data updated.");
shUserForm.getDataRange("F15").clearContent;
shUserForm.getDataRange("F17").clearContent;
shUserForm.getDataRange("F19").clearContent;
shUserForm.getDataRange("F21").clearContent;
shUserForm.getDataRange("F23").clearContent;
shUserForm.getDataRange("F25").clearContent;
valuesFound=true;
return;
}
if (valuesFound==false){
ui.alert("Your ID was not found.");
return;
}
}
}

Modification points:
I think that in your script, by return of if (valuesFound==false){}, when values[i][1]==str is false, the for loop is finished. By this, for example, when values[i][1]==str is false at the 1st row of values, the for loop is finished. I thought that this might be the reason of your issue.
About getDataRange(iRow, 5) of datasheet.getDataRange(iRow, 5), unfortunately, getDataRange has no arguments. I think that an error also occurs at this part.
About clearContent of shUserForm.getDataRange("F15").clearContent, in this case, the method of clearContent is not run.
If you want to search the value, how about the following modification?
Modified script 1:
When your script is modified, it becomes as follows.
function editRecord() {
var myGoogleSheet = SpreadsheetApp.getActiveSpreadsheet();
var shUserForm = myGoogleSheet.getSheetByName("Form");
var datasheet = myGoogleSheet.getSheetByName("Records");
var ui = SpreadsheetApp.getUi();
var response = ui.alert("Submit", 'Do you want to submit your data?', ui.ButtonSet.YES_NO);
var str = shUserForm.getRange("F7").getValue();
var values = datasheet.getDataRange().getValues();
var valuesFound = false;
if (response == ui.Button.NO) {
return;
}
for (var i = 0; i < values.length; i++) {
if (values[i][1] == str) {
var iRow = i + 1;
datasheet.getRange(iRow, 5).setValue(shUserForm.getRange("F15").getValue());
datasheet.getRange(iRow, 6).setValue(shUserForm.getRange("F17").getValue());
datasheet.getRange(iRow, 7).setValue(shUserForm.getRange("F19").getValue());
ui.alert("Data updated.");
shUserForm.getRange("F15").clearContent();
shUserForm.getRange("F17").clearContent();
shUserForm.getRange("F19").clearContent();
shUserForm.getRange("F21").clearContent();
shUserForm.getRange("F23").clearContent();
shUserForm.getRange("F25").clearContent();
valuesFound = true;
return;
}
}
if (valuesFound == false) {
ui.alert("Your ID was not found.");
return;
}
}
Modified script 2:
I thought that in your script, getValue and setValue are used in the loop. In this case, the process cost will become high. So, as another modified script, how about the following modification?
unction editRecord() {
var myGoogleSheet = SpreadsheetApp.getActiveSpreadsheet();
var shUserForm = myGoogleSheet.getSheetByName("Form");
var datasheet = myGoogleSheet.getSheetByName("Records");
var ui = SpreadsheetApp.getUi();
var response = ui.alert("Submit", 'Do you want to submit your data?', ui.ButtonSet.YES_NO);
var str = shUserForm.getRange("F7").getValue();
if (response == ui.Button.NO) {
return;
}
var res = datasheet.getRange("B1:B" + datasheet.getLastRow()).createTextFinder(str).matchEntireCell(true).findNext();
if (!res) {
ui.alert("Your ID was not found.");
return;
}
var [[v1],,[v2],,[v3]] = shUserForm.getRange("F15:F19").getValues();
datasheet.getRange(res.getRow(), 5, 1, 3).setValues([[v1, v2, v3]]);
shUserForm.getRangeList(["F15","F17","F19","F21","F23","F25"]).clearContent();
SpreadsheetApp.flush();
ui.alert("Data updated.");
}
In this sample, the value is searched using TextFinder. And, the cells are cleared using the RangeList.
References:
getRange(row, column)
clearContent()
createTextFinder(findText)

Related

Problem with the order of IF statements Google Script

I am struggling to set up my IF statements properly, basically I want to check if there is data in column J, if empty continue on with the rest of the script, if there is data ask if the user wants to continue, and then either continue or terminate depending on their response.
Here is the start of my code:
function exportSg() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Subgrade'); //source sheet
var testrange = sheet.getRange('J18:J'); //range to check
var testvalue = (testrange.getValues());
var ui = SpreadsheetApp.getUi();
for (i=0; i<testvalue.length;i++) {
if ( testvalue[i] != "") {
var response = ui.alert("Gaps greater than 50 meters identified in report, unhide column J to identify. \n\n Do you want to cancel the export?",ui.ButtonSet.YES_NO);
Logger.log(response);
if (response == ui.Button.YES) {
SpreadsheetApp.getActive().toast("Export Cancelled...")}else {
SpreadsheetApp.getActive().toast("Exporting Subgrade Report...");
The problem seems to be when there is no data in column J it does not continue with the export.
Anyone fancy shedding some light??
As mentioned in a previous answer to you, you can use a break to stop a loop.
In your case, that means to have a break when the user clicks the YES button:
if (response == ui.Button.YES) {
SpreadsheetApp.getActive().toast("Export Cancelled...");
break;
}
If you want to export even if there is no data to be used by the for statement, you need to move the export part to outside of it:
function exportSg() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Subgrade'); //source sheet
var testrange = sheet.getRange('J18:J'); //range to check
var testvalue = (testrange.getValues());
var ui = SpreadsheetApp.getUi();
var shouldExport = true;
for (i=0; i<testvalue.length;i++) {
if ( testvalue[i] != "") {
var response = ui.alert("Gaps greater than 50 meters identified in report, unhide column J to identify. \n\n Do you want to cancel the export?",ui.ButtonSet.YES_NO);
Logger.log(response);
if (response == ui.Button.YES) {
SpreadsheetApp.getActive().toast("Export Cancelled...");
shouldExport = false;
break;
}
}
}
if (shouldExport) {
myExportFunction();
}
}

Deleting Row - Google Apps Script "Cannot find method deleteRow((class))"

Here is my function:
function deleteContact(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var wsname = ss.getRangeByName("contactName").getValue();
var wscl = ss.getSheetByName('Connection List');
var wsRange = wscl.getRange("A1:A").getValues();
for(var i = 0; i<wsRange.length;i++){
if(wsRange[i][0] == wsname){
var row = i+1; //This gets the row number
}
var ui = SpreadsheetApp.getUi();
var response = ui.alert("Are You Sure You Want To Delete " + wsname, ui.ButtonSet.YES_NO);
if (response == ui.Button.YES) {
var delr = wscl.deleteRow(row);
}
}
}
I am able to get the row number, but once I click the 'YES' button, I am getting this error:
Cannot find method deleteRow((class)).
Any suggestions? I want the function to delete the row it found.
Try this:
function deleteContact(){
var ss=SpreadsheetApp.getActive();
var wsname=ss.getRangeByName("contactName").getValue();
var wscl=ss.getSheetByName('Connection List');
var wsValues=wscl.getRange(1,1,wscl.getLastRow(),1).getValues();
var d=0;
for(var i=0;i<wsValues.length;i++){
if(wsValues[i][0]==wsname){
var row=i+1-d; //This gets the row number
var ui=SpreadsheetApp.getUi();
var response=ui.alert("Are You Sure You Want To Delete " + wsname, ui.ButtonSet.YES_NO);
if (response == ui.Button.YES) {
wscl.deleteRow(row);
d++;
}
}
}
}
As you delete the rows the ones that are left move up a row each time but the data was taken before the rows were deleted so the indices for calculating the address from the data array have to be compensated by the number of rows that have been deleted.
You also had some lines that should have been within the scope of if(wsValues[i][0]==wsname){ and were not so I moved them in and now it only deletes lines with the correct name.

I want to write a script to read and log values of a specific row and copy them to a new sheet

I want to build a script to automate creating a form out of specific values.
What I need to do is:
Ask the user which row is relevant for the form, enter the row in a text box
Log value of column A of that row (Date)
Check that row from column C-ZZZ for values (numbers)
if there is a cell with a value, log the value. If the cell is empty ignore it
If there is a value in a cell additionally log values of row 1-10 of that column (numbers + strings)
create a new sheet
copy logged values in a specific order into that new sheet (other order than in the first sheet)
I searched for scripts that offer any resemblance of what I want to do, but I only managed to copy values of a specific row and output them into a new sheet without any formatting
Ask user for a row (NaN error message doesn't work):
function tourzettelZeile() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var rowIdxStr = Browser.inputBox("For which row do you want to make the list?");
if (rowIdxStr == 'cancel') {
return;
}
var rowIdx = parseInt(rowIdxStr);
if (rowIdx == NaN) {
Browser.msgBox("Numbers only or 'cancel'");
return;
}
}
My attempt at logging and pasting data:
function readData(spreadsheetID)
{
var spreadsheetId = '1KdZNvKgwL6NMuF0FYGB8jBhRVpbwv954D_UcBZ22eh0';
var plakatMenge = Sheets.Spreadsheets.Values.get(spreadsheetId, 'PlakatTool2019!DK15:DK900');
var plakatFormat = Sheets.Spreadsheets.Values.get(spreadsheetId, 'PlakatTool2019!DK4');
var plakatName = Sheets.Spreadsheets.Values.get(spreadsheetId, 'PlakatTool2019!DK15:DK1');
var plakatiererName = Sheets.Spreadsheets.Values.get(spreadsheetId, 'PlakatTool2019!FK66');
var plakatInfo = Sheets.Spreadsheets.Values.get(spreadsheetId, 'PlakatTool2019!DK7');
var plakatGebiet = Sheets.Spreadsheets.Values.get(spreadsheetId, 'PlakatTool2019!FL66');
var auftragDatum = Sheets.Spreadsheets.Values.get(spreadsheetId, 'PlakatTool2019!A66');
Logger.log(plakatMenge.values);
Logger.log(plakatFormat.values);
Logger.log(plakatName.values);
Logger.log(plakatiererName.values);
Logger.log(plakatInfo.values);
Logger.log(plakatGebiet.values);
Logger.log(auftragDatum.values);
Browser.msgBox(plakatMenge + plakatFormat + plakatName + plakatiererName + plakatInfo + plakatGebiet + auftragDatum);
//Neues Sheet erstellen
var requests = [{
'addSheet': {
'properties': {
'title': 'Tourzettel',
'gridProperties': {
'rowCount': 80,
'columnCount': 14
},
'tabColor': {
'red': 1.0,
'green': 0.3,
'blue': 0.4
}
}
}
}];
var response =
Sheets.Spreadsheets.batchUpdate({'requests': requests}, spreadsheetId);
Logger.log('Created sheet with ID: ' +
response.replies[0].addSheet.properties.sheetId);
//writeValues
// Specify some values to write to the sheet.
var ss = SpreadsheetApp.getActiveSpreadsheet();
// ss is now the spreadsheet the script is associated with
var sheet = ss.getSheets()[5];
var values = [
[plakatMenge.values, "Gebiet", plakatFormat.values, plakatName.values, plakatiererName.values, plakatInfo.values, plakatGebiet.values, auftragDatum.values]
];
var range = sheet.getRange("A1:H1");
range.setValues(values);
}
Both these scripts don't do the right thing and are not combined (info on which row to check does nothing)
This function attempts to do the first five items in your list:
Feel free to use and debug it because I have not debugged it at all.
function readAndLog() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var row=SpreadsheetApp.getUi().prompt('Enter Row Number').getResponseText();
if(typeof(row)!=Number) {
throw("Invalid Response: terminating script");
return;
}else{
Logger.log(sh.getRange(row,1).getValue());
var vA=sh.getRange(row,3,1,sh.getLastColumn()-3).getValues();
var rA=[];
for(var i=0;i<vA.length;i++) {
if(vA[0][1] && typeOf(vA[0][i])==Number) {
rA.push({col:i+1,val:vA[0][i]});
Logger.log('column: %s, value: %s',rA[i].col,rA[i].val);
var cA=sh.getRange(1,rA[i].col,10,1).getValues();
for(var j=0;j<cA.length;j++) {
Logger.log('col: %s, row: %s value: %s',rA[i].col, j+1, cA[0][j]);
}
}
}
}
}
Thanks, that helped a lot. I had to get rid of this part:
if(typeof(row)!=Number) {
throw("Invalid Response: terminating script");
return;
}else{
As it gave me an error, I wasn't able to solve.
I added a bit to the code:
function Tourzettel(){
readAndLog();
newSheet();
logOutput();
}
function readAndLog() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var row=SpreadsheetApp.getUi().prompt('Gib die Zeile mit dem Datum für die Tour ein').getResponseText();
Logger.log(sh.getRange(row,1).getValue());
var vA=sh.getRange(row,3,1,sh.getLastColumn()-3).getValues();
var rA=[];
for(var i=0;i<vA.length;i++) {
if(vA[0][1] && typeOf(vA[0][i])==Number) {
rA.push({col:i+1,val:vA[0][i]});
Logger.log('column: %s, value: %s',rA[i].col,rA[i].val);
var cA=sh.getRange(1,rA[i].col,10,1).getValues();
for(var j=0;j<cA.length;j++) {
Logger.log('col: %s, row: %s value: %s',rA[i].col, j+1, cA[0][j]);
}
}
}
}
// Create new sheet
function newSheet() {
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var yourNewSheet = activeSpreadsheet.getSheetByName("TourzettelV2");
if (yourNewSheet != null) {
activeSpreadsheet.deleteSheet(yourNewSheet);
}
yourNewSheet = activeSpreadsheet.insertSheet();
yourNewSheet.setName("TourzettelV2");
}
// Log in das neue Sheet schreiben
function logOutput() {
var stringToWrite = Logger.log;
SpreadsheetApp.getActive().getSheetByName('TourzettelV2').getRange("A1").setValue(stringToWrite);
}
My problem now is the function logOutput, because Logger.log doesn't seem to work. It outputs:
function log() {
[native code, arity=0]
}
Not sure what exactly it logged there, but it doesn't look like cell values. I do want every single logged value to be put into a seperate cell.
To fix the NaN error try this:
if (isNan(rowIdx)) {
Browser.msgBox("Numbers only or 'cancel'");
return;
}
The other part is a little hard to follow. If you want to pass the row index number to the second function, you could try adding this line to the first function:
readData(rowIdx);
And the second function would then be
function readData(rowIdx) {
...
}
And it can then use the rowIdx variable in the script. I'm assuming the 2nd function does not need the spreadsheetID passed to it as it is provided in the first line of the function. (Or is that there because you were testing?)

How do I call a mailApp function within an onEdit function?

I have an onEdit function where if executed should send an email. However, some research has turned up that the mailApp functions don't work with onEdit. I'm currently toying around with workarounds but wondering if others have solutions they've come up with.
function onEdit(e) {
var sheet = e.source.getActiveSheet();
var sheetName = sheet.getName();
//if editing specific sheet
if (sheetName == "draft_test" ) {
//if editing 6th column
var r = e.source.getActiveRange();
if (r.getColumn() == 6 ) {
var player = sheet.getActiveCell().getValue();
// Display a dialog box with a message and "Yes" and "No" buttons.
var ui = SpreadsheetApp.getUi();
var response = ui.alert('Do you want to draft '+player+'?', ui.ButtonSet.YES_NO);
// Process the user's response.
if (response == ui.Button.YES) {
//***********FOR SOME REASON EXECUTING THIS FUNCTION ISNT WORKING******************
emailLeague();
//sortAutoRoster();
} else {
}
}
}
}
function emailLeague() {
var draftSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("draft_test");
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("test_email");
var emailSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Manager Info");
//actual email address omitted
var recipient = 'emailaddress#gmail.com';
//get array of all picks on "draft test" tab, and find latest one
var picks = draftSheet.getRange(3,6,146,1).getValues();
for (var i=0; i<picks.length; i++){
if (picks[i] == ""){
var latestPick = i;
break;
}
}
var subject = sheet.getRange(i+1, 2).getValue();
var body = sheet.getRange(i+1, 1).getValue();
MailApp.sendEmail(recipient, subject, body);
}

Google Apps Script - Make onEdit() recognize setValue() changes

I have a Spreadsheet with some functions. One of them is a onEdit(event) function that copies some values to other sheets based on conditions. This is the code (simplified but with the important parts intact):
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.range;
if(s.getName() === "Lista" && r.getColumn() === 9 && r.getValue() === "Posicionada") {
var sheetname = s.getRange(r.getRow(),3).getValue();
var columnRef = s.getRange(r.getRow(),4).getValue();
var row = s.getRange(r.getRow(),5).getValue();
var targetSheet = ss.getSheetByName("Mapa " + sheetname);
var headers = targetSheet.getRange(1, 1, 1, targetSheet.getLastColumn());
for (var i = 0; i < headers; i++) {
if (headers[i] === columnRef) {
break;
}
}
var column;
if (columnRef === "A1") {
column = 2;
}
else if (columnRef === "A2") {
column = 3;
}
else if (columnRef === "B1") {
column = 4;
}
else if (columnRef === "B2") {
column = 5;
}
if (sheetname === "N2") {
row = row - 30;
}
if (sheetname === "N3") {
column = column - 10;
row = row - 42;
}
targetSheet.getRange(row,column).setValue(s.getRange(r.getRow(), 1, 1, 1).getValue());
}
}
The code works as it should when I manually edit the cell. But, I have a code that edit the cell when the user press a button in a sidebar, this is the code:
function positionMU(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cell = ss.getActiveCell().activate();
var cellLevel = cell.offset(0,2);
var cellLetter = cell.offset(0,3);
var cellNumber = cell.offset(0,4);
var cellStatus = cell.offset(0,8);
var dbq = "Posicionada";
var fora = "Pendente de recebimento";
if (cellStatus.getValue() == "Aguardando posicionamento"){
cellStatus.setValue(dbq); //attention in this line
}
else if (cellStatus.getValue() == "Aguardando saída"){
cellStatus.setValue(fora);
var cellExitDate = cell.offset(0,6);
cellExitDate.setValue(getDate());
}
}
As you can see, this function change the cell content with setValue(), but, when I use this function, the value of the cell changes, but the onEdit() trigger doesn't work.
How can I make the onEdit() trigger recognize changes made with setValue()?
You are right. onEdit() only triggers if the range is edited manually. As can be seen here, onEdit() triggers when a value is changed by the user.
I tested the function by making function to insert values into a column for which my onEdit responds and nothing happens. Including various other techniques that I could think of. Best thing to do here is to suggest this as an enhancement on App Script's Issue Tracker.
However, I made it work by writing another function to be called when another function in the script makes changes to the sheet. These are the test functions I wrote:
function addValues()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
var range = sheet.getDataRange();
var book = "Book";
var cancel = "Cancel";
var maxRow = range.getLastRow()+1;
for(var i=0; i<4; i++)
{
if (i%2 == 0)
{
sheet.getRange(maxRow, 1).setValue(book);
autoChanges(maxRow);
}else{
sheet.getRange(maxRow, 1).setValue(cancel);
autoChanges(maxRow);
}
maxRow++;
}
}
autoChanges function:
function autoChanges(row)
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
var range = sheet.getDataRange();
var data = range.getValues();
var response = "";
sheet.getRange(row, 2).protect();
response = data[row-1][0];
if (response == "Book")
{
sheet.getRange(row, 2).canEdit();
}else{
sheet.getRange(row, 2).setValue("--NA--");
}
}
Not the most elegant solution but this seems to be the only workaround for what you are trying to do.
There are some very good reasons why calling range.setValue() doesn't trigger the onEdit event, and most of them have to do with infinite recursion. In fact, you call setValue() yourself WITHIN onEdit(). This would trigger a recursive call, and from what I can see, you have no provision for handling the base case and thus your code would explode if setValue() did what you want.
Why not simply take all of your code out of your event handler, and put it into another function:
function onEdit (e) {
return handleEdits(e.range);
}
function handleEdits(r) {
s = r.getSheet();
ss = s.getParent();
//the rest of your code should drop right in.
}
then, inside your autoChanges function, go ahead and call handleEdits, passing it an appropriate range, after your call to setValue().
If you like to play with fire, and I personally do, you can call the onEdit(e) function after you make a change. Just send in an object whatever is called and used by the e object.
For me, I just needed to add:
var e={};
e.range=the range you are making a change to in the script;
e.source = SpreadsheetApp.getActiveSpreadsheet();//or make sure you have the sheet for wherever you are setting the value
e.value=whatever value you are setting
e.oldValue=if you onEdit needs this, set it here
//if you are using any of the other standard or special properties called by your onEdit...just add them before calling the function.
onEdit(e);//call the function and give it what it needs.