I have a function in a spreadsheet based script that is triggered when a submission is made with the spreadsheet form :
function onEntry(e){
Logger.log(e);
MailApp.sendEmail("scriptadmin#uniben.edu", "New Mail Request", "Someone submited data");
}
How can I reject the entry, say if it's a duplicate entry ?
Using the documentation on events you will have to choose what data you want check (user name, specific field...) and compare that to data already in the spreadsheet.
You should do these iterations on an array level since it will be far more efficient and fast, you can get data in an array using something like
var data = SpreadsheetApp.openById(key).getDataRange().getValues();
You could also use javascript function like indexOf() that will return -1 if no match if found or item position in the array if a match is found.
Actually there are many ways to do that but your question is too vague to know what will be the best...
EDIT : following your comment, I'd suggest you let the duplicate form data come into the sheet and then use a script to remove duplicates. You could run this script on a on form submit trigger or on a timer to let it run daily or hourly, and send the email only if the last entry was a new one (no duplicates found)... depending on your use case.
There is a script in the gallery that does the job pretty well, it was written by Romain Vialard, a GAS TC that has contributed a lot. (the link above goes to the script description but you can get it also in the public gallery, just search for 'remove duplicates' you'll see that other scripts do that, all the scripts in the gallery have been checked by the GAS team)
4 months late, but better late than never. I believe this function does almost what was originally requested. i.e. "How do I prevent the entry from entering the spreadsheet if I decide that it's a duplicate." It is not precisely what was requested, but very close.
This code checks one column against that same column in another sheet, for all rows in that sheet. Lets say you have a list of companies or clients on a sheet. That list includes name, phone, address, etc. etc. Lets say you want to check against the phone number - if the phone number you are currently entering is already on your client sheet, then don't allow entry - or more precisely clear it out immediately upon entering it.
I'm sure the more experienced members will be able to point out flaws, but it works for me.
I believe it will also work for the case where a phone number in the middle of the sheet is changed - so it's not just last line that gets checked, it's the line that gets edited that gets checked - I've not tested this particlar scenario. Also, I made some changes to variable names to protect the innocent...hopefully I didn't mess anything up while doing that.
I call this function from within another function that is triggered by onEdit. Theoretically it should be able to be installed as an onEdit trigger itself. I hope someone finds it useful.
function checkNewEntryForDuplicate(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var entrySheet = SpreadsheetApp.getActiveSheet();
var clientSheet = ss.getSheetByName("Clients");
var r = entrySheet.getActiveCell();
var lastCol = entrySheet.getLastColumn();
// If this had any consistency, we'd be able to get the row from entrySheet the same
// as we get column. But there is no getRow() method at the sheet level.
var rowNum = r.getRow();
var clientData=clientSheet.getDataRange().getValues();
var phoneColumnOffset=getPhoneColumnOffset(); // You'll need to get the offset elsewhere. I have a function that does that.
var columnNum=e.range.getColumn(); // column that is currently being edited
if (columnNum != phoneColumnOffset+1) // no point in doing anything else if it's not the column we're interested in.
return 0;
var entryRow=entrySheet.getRange(rowNum, 1, 1, lastCol);
var phoneNum = e.range.getValue();
// iterate over each row in the clientData 2-dimensional array.
for(i in clientData){
var row = clientData[i];
var duplicate = false;
// For each row this conditional statement will find duplicates
if(row[phoneColumnOffset] == phoneNum){
duplicate = true;
var msg="Duplicate Detected. Please do not enter. Deleting it..."
Browser.msgBox(msg);
entryRow.clearContent();
entryRow.clearComment();
return duplicate;
}
}
return duplicate;
}
I am doing the same things but having no scripts at all and just by spreadsheet functions. That kind of things are just like SQL for me and very interest to do.
For your question, this link will help: http://www.labnol.org/software/find-remove-duplicate-records-google-docs/5169/
Related
I made a Google Sheet to check every media that plays on a certain channel on TV using a lot of workaround formulas within the cells themselves. A part of this sheet is a column (G) that tells me whether or not the specific episode/media/whatever is currently playing, has played in the past or will be played later today/at a later date using a "NOW" function. Next to that column there is another (F) where the user is able to write a "V", and in the case the show is playing but the user hasn't checked it yet, it writes "Check Me" See Example.
I wanted to create a button that will automatically change that "Check Me" into a "V" but the problem is that "Check Me" is based on a simple formula written throughout column F (=IF(G5="Playing","Check Me","")), so when I tried to run a script I found here on StackOverflow:
function Test() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("F5:F700");
range.setValues(range.getValues().map(function(row) {
return [row[0].replace(/Check Me/, "V")];
}));
}
(Can't remember the exact thread I got it from and it's been two days since I found it with lots of similar searches in my history, so I apologize for not crediting)
together with its intended use, it also straight up deleted all the rest of the formulas from the column, probably due to the formula itself containing "Check Me" but I might be mistaken.
To be honest, before this week I barely ever worked with either Google Sheets, much less JavaScript or even coding in general, so I'm pretty much restrained to changing values and very minor modifications in scripts I find online.
The only idea I had as to how to solve it is to add an "if IsBlank" but regarding face value of the cell only rather than its contents, but I don't know how to do it or whether it is even possible in the first place. At the very least, google shows no results on the subject. Is there a way to add that function? or perhaps a different method altogether to make that button work? (it's a drawing I will assign a script to)
Because you're using a map function to update the range, you'll need to get the formulas using getFormulas() in addition to the display values using either getValues() or getDisplayValues(). Using only the display values, as you're currently doing, will cause you to lose the formulas when you update the sheet. Conversely, using only the formulas would cause you to lose all of the display values that don't have a formula, so you'll need both. Try this and see if does what you want:
function Test() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("F5:F700");
// Get all cell formulas and display values
var formulas = range.getFormulas();
var values = range.getValues();
range.setValues(formulas.map(function(row, index) {
var formula = row[0];
var value = values[index][0];
// Check only the display value
if (value == "Check Me") {
return ["V"];
} else {
// Return formula if it exists, else return value
return [formula || value];
}
}));
}
I am new to Google scripting, so I apologize if this is a naive question. I do not know how to get a variable reference to a sheet within a spreadsheet that is not the first sheet.
In my spreadsheet, I have two sheets, Agenda and Info. Agenda is the first sheet(index 0) and Info is the second. I can get a reference to Agenda, but I cannot get a reference to Index. This is the code I have tried:
var info_sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Info');
info_sheet.getName() always comes out being Agenda, though. What do I do?
There are 2 ways to get access to a specific sheet in a spreadsheet : by its name as you were showing in your code or by its index like in the second example below.
Just run this test to see the results on a spreadsheet with at least 2 sheets (I changed the sheet names to the 'standard default values in US locale' to make the test working for anyone, re-set it to your parameters if needed)
function testSheetNames(){
var info_sheet_example1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var info_sheet_example2 = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];
Logger.log('name method result = '+info_sheet_example1.getName());
Logger.log('index method result = '+info_sheet_example2.getName());
}
That said, your example should return the correct value, I'm not sure why it didn't in your tests.
I have Written Script on Google Spreadsheet to send Email when spreadsheet is modified or any Data is added. Email Trigger is working but whenever any data is entered in next Row it send Email to previous email address also.
Please suggest solution
The below is written script :
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var numRows = 1; // Number of rows to process
var dataRange = sheet.getRange(startRow, 1 , numRows,3) // Fetch the range of cells A2:B3
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var emailAddress = row[2]; // First column
var message = row[0] + "requested" + row [1]; // Second column
var subject = "Sending emails from a Spreadsheet";
MailApp.sendEmail(emailAddress, subject, message);
}
}
Your question is unclear... nowhere in the script I see something that reads which cell is actually modified... your target range is hardcoded on row 2 so the only row that can be processed is row 2 (and the mail can only be sent once)...
So can you :
explain how it should work
explain how it works now , especially what do you mean by 'previous email'
remove typos in your code (row[2] is not First column)
explain how you trigger this function : the name onEdit(e) suggest an onEdit trigger but simple triggers cannot send mail so I suppose you have set some other trigger.
explain why (e) in your function parameter and not using it ?
EDIT : thanks for the complement of information.
The script you suggest is not sufficient to achieve what you want. The idea here is to check if something in the sheet has been modified either by adding (or inserting) a row of data or (if I understood well) by editing any row in the sheet with a new value.
This is not really as simple as it looks at the first glance ;-)
What I would do it to take a 'snapshot' of the sheet and -based on a timer or onEdit - compare that snapshot to the sheet's current state.
There is more than one way to get that result, you could have a second sheet in your spreadsheet that no one could modify and that is a copy of the main sheet that you update after each modification/mail send. So before updating the script should look for any difference between the sheets and send a report to the corresponding email when a difference is found.
Another way to do that is to store the sheet data converted to a string in the script properties, the principle is the same but it's more 'invisible' for normal users accessing the spreadsheet.
You could also use scriptDb or your userproperties but the script properties is probably better suited (simpler) for this use case.
Tell us what you think/prefer and I (or someone else) could probably give you some code to start with.
It appears that you're using a shared spreadsheet to collect the add-user-requests, and trusting the requesters to fill in the information. In the detail document you shared, it further appears that requests are ADDED, but not EDITED. (That's an important simplifying distinction.)
I suggest that what you really need is to use a form for receiving that input. Using a form will create a "data table" within your spreadsheet, a set of columns that you must not mess with. (You can edit the contents, add and delete rows, but must not add or remove columns.) However, you CAN add columns to the spreadsheet outside of this table, which gives you a handy place to store state information about the status of individual requests.
Further, you can trigger your processing to run on form submit, rather than a simple "onEdit" - this gets away from the problem that ScampMichael pointed out. Alternatively, you can use an installable edit trigger, as described in this answer.
Try this sheet, and this form. Save yourself a copy, go into the script and remove the comments that are stopping emails from being sent, and try it out. There's a menu item in the spreadsheet that can kick off processing; just clear the "Request State" column to re-run it. You can open the form (and find its URL), and add more entries to experiment.
It's the core of a similar system that I've written, and contains a discreet state machine for processing the requests. My system has large amounts of very complex data in multiple spreadsheets, so it often gets pre-empted, then needs to run again. (I use a timed trigger for that.) That's why requests are handled through states. If you find that too complex, pull out only the parts you need.
I wrote a custom google app script function in a script associated with my google doc spreadsheet. The function calls a third party service to get data. I can put the function in a cell:
=myfunction("something")
and it returns the correct value from the service. However, how can I keep this value updated so that it's showing the latest data from the service?
Update
For example:
=temperature("90120")
For getting the current temperature in a given zip code. Also my sheet may have dozens or hundreds of these so I'd prefer something that is performant and maintainable. It doesn't truly need to be continuous, polling once a minute or ideally more frequently could work. I'm wondering if there's some way from the script to set a timer to run to update a range of cells?
Not sure why you need dozens or hundreds.
1. Is the spreadsheet used by another process?
2. Is the spreadsheet visually reviewed by actual users?
If #1, you could replace the spreadsheet with a custom API via the content service to return JSON results for all temperatures.
If #2, you may hit limits or performance issues with so many functions firing so often. Why should fire the functions if no one is viewing the results. Alternatively, you could make it an on-demand with a custom menu option.
I have a similar problem.
This is how I am doing it atm, but its not the best solution. I am looking for a better one.
If any value at sheet Prices and column D changes.
Meaning if any cell value changes in the whole column it updates the custom function value.
//Search Price sheet with the given name. Return price. dummy param updates google ss once the "Prices" sheet values changed.
function searchPrice(price,dummy)
{
var SPREADSHEET_NAME = "Prices";
var SEARCH_COL_IDX = 2;
var RETURN_COL_IDX = 3;
var values = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SPREADSHEET_NAME).getDataRange().getValues();
for (var i = 0; i < values.length; i++)
{
var row = values[i];
if (row[SEARCH_COL_IDX] == price)
{
return row[RETURN_COL_IDX];
}
}
}
This is how you call it =searchPrice(B8,Prices!D:D)
Just give your custom function a dummy param. It doesn't do anything in the custom function.
I am programming a help desk system using google script, forms and spreadsheet.
To filter the queries the submissions are placed into different sheets depending on category, this is done through the FILTER function. however every time a new submission is made the filter function does not update, (it uses the CONTINUE function to cover the other cells)
instead the cell with the FILTER function must be selected and crtl+shift+E must be entered
is there a way around this?
I have tried two methods
the first was looking to have a function to enter the shortcut, but is this possible?
the second is auto entering the continue function everytime a new submission is made, I have this working however google sheets does not recognise the named range, (the continue function has the set up CONTINUE(original cell, rows away, columns away) its the original cell that it does not identify, instead I must manually select the cell and re-write the exact same cell reference.
Thank you for your help, if you need to see my code please ask :)
This is the code for the second option where I try to enter the function manually to the cells.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var numEntry = ss.getSheetByName('Home').getRange("B8").getValue() + 2;
var cat = ss.getSheetByName('Software problem').getRange(numEntry, 4, 1, 9);
cat.getCell(1, 1).setValue('=CONTINUE(D2, '+(numEntry-1)+', 1)');
Your option 1: Have a script enter keystrokes automatically? Not supported in apps-script.
Your Option 2: It shouldn't be necessary to programmatically insert CONTINUE, as the required CONTINUEs for your FILTER should be automatic, when rows in your filter range match the expressed criteria. Something else is wrong, so don't get caught up with this red herring.
You mention "google sheets does not recognise the named range" - I'd like to know what you mean by that, because I suspect this is where your solution will be. You can use named ranges within FILTER statements. You can also use open-ended ranges, like FormInput!A1:X or FormInput!E1:E.
If you're trying to manipulate named ranges using scripts, then you may have run into a known issue, "removeNamedRange() only removes named ranges that were created via Apps Script". (To get around that, manually delete the named range, then create it only from script.)
Here's a function I use to create a named range for all data on a sheet. You could adapt this to your situation. (I use this with QUERY functions instead of FILTER, you might want to consider that as an alternative.)
function setNamedRangeFromSheet(sheetName) {
// Cannot remove a named range that was added via UI - http://code.google.com/p/google-apps-script-issues/issues/detail?id=1041
var ss = SpreadsheetApp.getActiveSpreadsheet();
try { ss.removeNamedRange(sheetName) } catch (error) {};
var sheet = ss.getSheetByName(sheetName);
var range = sheet.getDataRange();
ss.setNamedRange(sheetName,range);
}
Using FILTER, you need to match the length of your sourceArray (which can be a named range) and any criteria arrays you use. To programmatically create a named range for a single-column criteria within your sourceArray, and of the same length, use getNumRows() on the sourceArray range.
Now, within your submission handling function, triggered on form submit, you'd have something like this. (I assume your trouble reports are coming into a single sheet, "FormInput" - adjust as necessary.)
...
var ss = SpreadsheetApp.getActiveSpreadsheet();
try { ss.removeNamedRange("FormInput") } catch (error) {};
var sheet = ss.getSheetByName("FormInput");
var inputRange = sheet.getDataRange();
ss.setNamedRange("FormInput",inputRange);
try { ss.removeNamedRange("Criteria") } catch (error) {};
var criteriaCol = 4; // Another guess, that Column E contains our criteria
var criteriaRange = sheet.getRange(0,criteriaCol,inputRange.getNumRows(),1);
ss.setNamedRange("Criteria",criteriaRange);
...
And with that in place, the content of A1 on your "Software problem" sheet just needs to contain the following. (Assuming that you're looking for "Bug"s.):
=FILTER(FormInput,Criteria="Bug")
I mentioned open-ended ranges earlier. If you aren't doing enough manipulation of data to justify named ranges, you could set up your filter like this, and not have to change it as new input came in:
=FILTER(FormInput!A1:X,FormInput!E1:E="Bug")