I am very new at writing code and have a limited understanding so please be gentle!
I have been trying to expand on the code made on this question.
I have had limited success and am now stuck, I was hoping someone would be kind enough to point me in the right direction or tell me what I am doing wrong.
So, the scenario: A need to have an auto-incrementing "Job Reference" for booking in netbook repairs to the IT dept I work for. This booking is made via a Google Form and I can get the code in the link to work perfectly but! I was hoping to have a little more than a simple count - 1,2,3,4,5 and so on. Ideally the job ref would be displayed as JR0001, JR0002 and so on.
So, my attempt at code!
The code which user: oneself submitted which works perfectly.
function onFormSubmit(e) {
// Get the active sheet
var sheet = SpreadsheetApp.getActiveSheet();
// Get the active row
var row = sheet.getActiveCell().getRowIndex();
// Get the next ID value. NOTE: This cell should be set to: =MAX(A:A)+1
var id = sheet.getRange("P1").getValue();
// Check of ID column is empty
if (sheet.getRange(row, 1).getValue() == "") {
// Set new ID value
sheet.getRange(row, 1).setValue(id);
}
}
The first thing I tried was to simply add another variable and add it to the .setValue
function onFormSubmit(e) {
// Get the active sheet
var sheet = SpreadsheetApp.getActiveSheet();
// Get the active row
var row = sheet.getActiveCell().getRowIndex();
// Get the next ID value. NOTE: This cell should be set to: =MAX(A:A)+1
var id = sheet.getRange("X1").getValue();
// Set Job Reference
var jobref = "JR";
// Check of ID column is empty
if (sheet.getRange(row, 1).getValue() == "") {
// Set new ID value
sheet.getRange(row, 1).setValue(jobref+id);
}
}
This worked as far as getting "JR1" instead of 1 but the auto-increment stopped working so for every form submitted I still had "JR1" - not really auto-increment!
I then tried setting up another .getValue from the sheet as the Job Ref
function onFormSubmit(e) {
// Get the active sheet
var sheet = SpreadsheetApp.getActiveSheet();
// Get the active row
var row = sheet.getActiveCell().getRowIndex();
// Get the next ID value. NOTE: This cell should be set to: =MAX(A:A)+1
var id = sheet.getRange("X1").getValue();
// Set Job Reference
var jobref = sheet.getRange("X2").getValue();
// Check of ID column is empty
if (sheet.getRange(row, 1).getValue() == "") {
// Set new ID value
sheet.getRange(row, 1).setValue(jobref+id);
}
}
Same result - a non incrementing "JR1"
I then tried concatenating the working incrementing number with my job ref cell and then calling that in the script.
function onFormSubmit(e) {
// Get the active sheet
var sheet = SpreadsheetApp.getActiveSheet();
// Set Job Reference
var jobref = sheet.getRange("X2").getValue();
// Get the active row
var row = sheet.getActiveCell().getRowIndex();
// Get the next ID value. NOTE: This cell should be set to: =MAX(A:A)+1
var id = sheet.getRange("X1").getValue();
// Check of ID column is empty
if (sheet.getRange(row, 1).getValue() == "") {
// Set new ID value
sheet.getRange(row, 1).setValue(jobref);
}
}
Same result the value doesn't increment
I don't understand why the number stops incrementing if I add other variables and don't understand how to add the leading zeros to the incrementing number. I get the feeling that I am trying to over complicate things!
So to sum up is there a way of getting an auto-incrementing ref that is 6 characters long - in this scenario first form JR0001 second submit JR0002 and so on.
I would really like some pointers on where I am going wrong if possible, I do want to learn but I am obviously missing some key principles.
here is a working solution that uses the "brand new" Utilities.formatString() that the GAS team just added a few days ago ;-)
function OnForm(){
var sh = SpreadsheetApp.getActiveSheet()
var startcell = sh.getRange('A1').getValue();
if(! startcell){sh.getRange('A1').setValue(1);return}; // this is only to handle initial situation when A1 is empty.
var colValues = sh.getRange('A1:A').getValues();// get all the values in column A in an array
var max=0;// define the max variable to a minimal value
for(var r in colValues){ // iterate the array
var vv=colValues[r][0].toString().replace(/[^0-9]/g,'');// remove the letters from the string to convert to number
if(Number(vv)>max){max=vv};// get the highest numeric value in th column, no matter what happens in the column... this runs at array level so it is very fast
}
max++ ; // increment to be 1 above max value
sh.getRange(sh.getLastRow()+1, 1).setValue(Utilities.formatString('JR%06d',max));// and write it back to sheet's last row.
}
Related
A sheet populated with results of a query, from a separate workbook. User can select values from a drop down for any cell. When onEdit called, value is transferred to the main workbook, which then replicates back to the current cell in the active sheet via the query. Trying to prevent cells with existing values from being edited - ie - user can only put a value in a currently blank cell, and not change an existing. Because the query repopulates with each change in the master, locations of empty cells constantly change as they are filled or if new rows are added in the master.
(code below is a mashup from this video and one similar about unique ID's..(https://www.youtube.com/watch?v=ucLO4iHP2Kw))
Tried reading the current range of data into an array, and chugging through to set protection on any non-null cell. This works, but a 3x60 range of cells takes over 2 minutes to set protection on the non-blanks. Not a very good wait time before the user can move on to edit another cell!
Following is current code triggered on onEdit() The section after the "else" is where I want things to bail out by rejecting the user selection/input.
Any attempt to enter something different or change a value in a non-empty cell results in the query generating an error because it doesn't want to overwrite the cell. Hitting the "Del" key clears the cell, and the query then will refresh everything. Optimally, it would be great if the section after "else" just did the same - hit the "Del" key!
function syncData(e){
var src = e.source.getActiveSheet();
var r = e.range;
if (e.oldValue == null){
r.clear();
let id = src.getRange(r.rowStart,1).getValue();
var db = SpreadsheetApp.openById("SPREADSHEET ID").getSheetByName("SHEET NAME");
var ids = db.getRange("A:A").getValues();
let row = 0;
for (row; row < ids.length; row++){
if (ids[row][0] === id)
break;
}
row++;
db.getRange(row,r.columnStart).setValue(e.value);
}
else{
e.setValue(e.oldValue);
}
}
Of course I came to a solution after posting here!
If the rest worked, all I needed to do was post e.oldValue to the main spreadsheet, rather than e.value. That would repopulate the view sheet with the original value, preventing the user from making a change.
Here's the modified code:
function NewsyncData(e){
var src = e.source.getActiveSheet();
var r = e.range;
r.clear();
let id = src.getRange(r.rowStart,1).getValue();
var db = SpreadsheetApp.openById("SPREADSHEET ID").getSheetByName("SHEET NAMES");
var ids = db.getRange("A:A").getValues();
let row = 0;
for (row; row < ids.length; row++){
if (ids[row][0] === id)
break;
}
row++;
if (e.oldValue == null){
db.getRange(row,r.columnStart).setValue(e.value);
}
else{
db.getRange(row,r.columnStart).setValue(e.oldValue);
}
}
I've got limited knowledge of google script and I'm trying to get better every day. I'm trying to conditionally set the value of 2 cells based on the value in cells contained in other rows. I've got a sample sheet (here) with appointments on it. You'll also see the output desired on the output sheet.
When two or more appointments are taken by the same person based on his email address I want to write Yes in column Duplicate for every duplicate appointments but the most recent (based on Column E, that is the date when the appointment was created) and that are greater than the current date (if the appointment is already in the past no need to do anything). I also want to set the value of the column L to "Not Coming" which is a cell containing a data validation that I already automated on my main spreadsheet.
Here is the script that I already designed based on other questions answered here on stackoverflow. I'm not really familiar with indexes and how to proceed with them. The script runs without errors but nothing happens.
var currentDate = new Date()
// Master
var sheetMaster = ss.getSheets()[0];
var allValues=sheetMaster.getRange(2,1,sheetMaster.getLastRow()-1,sheetMaster.getLastColumn()).getValues();
var emailValues=sheetMaster.getRange(2,3,sheetMaster.getLastRow()-1,3).getValues();
var dateCreatedAtValues=sheetMaster.getRange(2,5,sheetMaster.getLastRow()-1,5).getValues();
var duplicateColumn=sheetMaster.getRange(2,11,sheetMaster.getLastRow()-1,11);
var eM=[];//emails
var dA=[];//dates
var eR=[];//entire rows
var dC=[];//duplicateColumn Yes or empty
function analyzeDuplicateEntries() {
for(var i=0;i<emailValues.length;i++) {
var idx=eM.indexOf(emailValues[i][0]);
if(idx==-1) {
eM.push(emailValues[i][0]);
dA.push(dateCreatedAtValues[i][0]);
eR.push(allValues[i]);
}
else if(new Date(dateCreatedAtValues[i][0]).valueOf() > new Date(dA[idx]).valueOf() && new Date(dateCreatedAtValues[i][0]).valueOf()> currentDate) {
duplicateColumn[i][0].setValue("Yes");
}
}
} ```
You are retrieving the wrong column and set the values to a range cell incorrectly
var mailValues=sheetMaster.getRange(2,3,sheetMaster.getLastRow()-1,3).getValues(); will return columns 3 to 5 (see documentation, while your emails are in column B, that is column 2.
Pay attention that the first parameter in getRange(row, column, numRows, numColumns) is the number of the columns to retrieve, rather than the last column
Mind that to use setValue on a range that contains more than one cell, you need to retrieve first the respective cell with getCell()
Take thereby into consideration that the cell indexes start with 1 (opposed to array indexes that start with 0`).
A simple (not optimal) way to rewrite your code would be:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var currentDate = new Date()
// Master
var sheetMaster = ss.getSheets()[0];
var allValues=sheetMaster.getRange(2,1,sheetMaster.getLastRow()-1,sheetMaster.getLastColumn()).getValues();
var emailValues=sheetMaster.getRange(2,2,sheetMaster.getLastRow()-1,1).getValues();
var dateCreatedAtValues=sheetMaster.getRange(2,5,sheetMaster.getLastRow()-1,1).getValues();
var duplicateColumn=sheetMaster.getRange(2,11,sheetMaster.getLastRow()-1,1);
var eM=[];//emails
var dA=[];//dates
var eR=[];//entire rows
var dC=[];//duplicateColumn Yes or empty
function analyzeDuplicateEntries() {
for(var i=0;i<emailValues.length;i++) {
var idx=eM.indexOf(emailValues[i][0]);
if(idx==-1) {
eM.push(emailValues[i][0]);
dA.push(dateCreatedAtValues[i][0]);
eR.push(allValues[i]);
}
else if(new Date(dateCreatedAtValues[i][0]).valueOf() > new Date(dA[idx]).valueOf() && new Date(dateCreatedAtValues[i][0]).valueOf()> currentDate) {
duplicateColumn.getCell(i+1, 1).setValue("Yes");
}
}
}
I have been making a script that finds a number (rank) associated with a nickname. I have created a custom function for this. Basically the formula input finds that input (a nickname) in an array of nicknames, it translates the array order into row number by adding 2(+1 because array starts at 0 and +1 because of the index of my column). After doing that it goes to the column where the rank number is located and that is what it returns.
The script works well but when I update the rank number the formula doesn't always return the new value (but if I run the formula again it does) so I suppose that the problem is that it either takes too long to execute it or my spreadsheet is not really recalculating every minute as I have set the settings.
What is the problem and how can I fix it? Thanks!
This is the code of the custom function:
/**
* Finds the rank of the inputed user
*
* #customfunction
*/
function FINDRECRUITRANK(Recruit_Name) {
var app = SpreadsheetApp;
var ss = app.getActiveSpreadsheet();
var display = ss.getSheetByName("DisplaySheet");
var lastRow = display.getRange("B133").getValue(); //B133 has a counter of total rows used
var allnicknamesbeta = display.getRange(2, 3, lastRow-1).getValues();
var allnicknames = allnicknamesbeta.map(function(r){ return r[0]; });
var index = allnicknames.indexOf(Recruit_Name) + 2; // +1 because array starts from 0 and +1 because of the first row index
var recruitRank = display.getRange(index, 4).getValue();
return recruitRank;
}
Update:
The first column is a list of names that have a number asigned to it(ranks). The 5th clumn has the recruit_name. What the function does is looks for th recruit_name in the first column, in this case it would be on (2,1) and then it copies the number assigned to it (rank, in this case on cell(2,2)). Thats what the function will return->2.
The problem comes when I change that 2 to a 3(or any other number !=2), now cell (2,2) would have a 3 but the function input which is cell (1,5) has not changed that's why the return doesn't get updated.
The return of the formula is not in the picture, but it could be for example in (1,6).
Custom functions are recalculated only when the spreadsheet is opened and when one of the arguments values are changed.
Related
Google sheet cell recalculation
Solution
If you want to have a function that updates everytime there is a change in the sheet instantly, what you should be looking for is for an onEdit() simple trigger.
In the following implementation, I am setting the value of the Recruit_Name in a allocated cell for that and returning the value of the Apps Script function in a different cell allocated also for that purpose. In this way, all the values will get updated automatically.
/**
* Finds the rank of the inputed user
*
* #customfunction
*/
function onEdit() {
var app = SpreadsheetApp;
var ss = app.getActiveSpreadsheet();
var display = ss.getSheetByName("DisplaySheet");
// Get your parameter you were getting before in the sheet in a specific cell of it
var Recruit_Name = display.getRange("F1").getValue();
var lastRow = display.getRange("B133").getValue(); //B133 has a counter of total rows used
var allnicknamesbeta = display.getRange(2, 3, lastRow-1,1).getValues();
var allnicknames = allnicknamesbeta.map(function(r){ return r[0]; });
var index = allnicknames.indexOf(Recruit_Name) + 2; // +1 because array starts from 0 and +1 because of the first row index
var recruitRank = display.getRange(index, 4).getValue();
// Show the result of all the operations in a specific cell allocated for this function
display.getRange('F2').setValue(recruitRank);
}
I hope this has helped you. Let me know if you need anything else or if you did not understood something. :)
The pictures used are only from an example sheet! My basic problem is that I have a list called Assignment in which names appear (dropdown list). For Location (in the assignment sheet) I use the following formula: =IF(C2<>"",VLOOKUP(C2,'Input Data'!C$3:D$7,2,FALSE),"")
These names are assigned certain values, they are in the same line. The names are defined in a worksheet called Input Data!
If I now delete a name like Green, John from the Input Data worksheet, then I get the following error in another worksheet (Evaluation). (More than 40 people have access to this worksheet and randomly delete names)In this evaluation worksheet the values are evaluated by the following formula:
=ARRAY_CONSTRAIN(ARRAYFORMULA(SUM(IF((IF($B$2="dontcare",1,REGEXMATCH(Assignment!$E$3:$E$577,$B$2 &"*")))*(IF($B$3="dontcare",1,(Assignment!$E$3:$E$577=$B$3)))*(IF($B$4="dontcare",1,(Assignment!$D$3:$D$577=$B$4)))*(IF($B$5="dontcare",1,(Assignment!$F$3:$F$577=$B$5)))*(IF($B$6="dontcare",1,(Assignment!$B$3:$B$577=$B$6))),(Assignment!S$3:S$577)))), 1, 1)
The following error appears in the evaluation sheet:
Error:
During the evaluation of VLOOKUP the value "Green, John" was not found.
How can I avoid this error? Is it possible to avoid this error with a macro that deletes Names from assignment sheet that are not in the Input data sheet? Do you have any ideas for a code?Maybe a Formula or perhaps a Macro?
example sheet with explanation: https://docs.google.com/spreadsheets/d/1OU_95Lhf6p0ju2TLlz8xmTegHpzTYu4DW0_X57mObBc/edit#gid=1763280488
If what you want to do is make sure that rows are deleted in a sheet when there are incorrect values you could try something like this in Apps Script:
function onEdit(e) {
var spreadsheet = e.source;
var assignment = spreadsheet.getSheetByName("Assignment");
var assignmentRange = assignment.getDataRange();
var assignmentNames = assignment.getRange(3, 2, assignmentRange.getNumRows());
var inputData = spreadsheet.getSheetByName("Input Data");
var inputDataRange = inputData.getDataRange();
var i = 1;
while(assignmentNames.getNumRows() > i){
var currentCell = assignmentNames.getCell(i, 1);
var txtFinder = inputDataRange.createTextFinder(currentCell.getValue());
txtFinder.matchEntireCell(true);
if(!txtFinder.findNext()){
assignment.deleteRow(currentCell.getRow())
}else{
// We are only steping when no elements have been deleted
// Otherwise we would skip rows due to shifting in row deletion
i++;
}
}
}
Explanation
onEdit is a special function name in Apps Script that would execute every time it's parent sheet is modified.
After that we retrieve the spreadsheet from the event object
var spreadsheet = e.source;
Now we get the relevant range in the Assignment sheet. Look at the usage of getDataRange documentation to avoid retrieving unnecessary cell values. And from that range we actually get the specific column we are interested on.
var assignment = spreadsheet.getSheetByName("Assignment");
var assignmentRange = assignment.getDataRange();
var assignmentNames = assignment.getRange(3, 2, assignmentRange.getNumRows());
Now we do the same for the other sheet(Input Data):
var inputData = spreadsheet.getSheetByName("Input Data");
var inputDataRange = inputData.getDataRange();
Note: Here I'm not getting a specified column because I assume that the full name will not repeat in any other column. But if you want you could get the specified range as I have done at Assignment.
After that we want to look for specific values in the Assignment range that don't exist in the Input Data sheet, you should try the TextFinder.
For every name in Assignment you should create a TextFinder. I have also forced to make a whole cell match.
var i = 1;
while(assignmentNames.getNumRows() > i){
var currentCell = assignmentNames.getCell(i, 1);
var txtFinder = inputDataRange.createTextFinder(currentCell.getValue());
txtFinder.matchEntireCell(true);
If txtFinder finds a value the findNext() will evaluate to true. In the other hand when the txtFinder does not find a value it will be null and evaluated to false.
if(!txtFinder.findNext()){
assignment.deleteRow(currentCell.getRow())
}else{
// We are only stepping forward when no elements have been deleted
// Otherwise we would skip rows due to shifting in row deletion
i++;
}
}
}
I'm very new and I'm trying to create a Time in/Time out sheet. I have 2 separate sheets, first(ACTIVE) is where the trigger happens that starts the onEdit(e) script. All the functions that start onEdit(e) affects the second sheet(PASSIVE) to fill out Columns A(Last Name), B(First Name), C(Location), D(Time Out). I finished making the Time out functions by getting value of A, B, C + Active Row(this isn't the code). The trigger is always on the same row as the values being copied, so it was relatively simple. On the PASSIVE sheet I have all the values being stored using a code someone made called addRecord where it gets last row + 1 of the PASSIVE sheet and installs the values grabbed from the ACTIVE sheet and plugs them in. So it adds records without overwriting anything. Works beautifully. However making a "time in" function has been difficult. E(Time In) My idea is to getRow of the PASSIVE sheet by searching PASSIVE!A for the Value grabbed from (ACTIVE!A + Active Row) once it finds a match, it sees if (PASSIVE!E + the matched row) is empty. If it is, it adds new.Date and finishes. If it isn't empty, it ignores this row and continues searching down the line for the next Row that has PASSIVE!A match the grabbed value. Once it finds this Row, getRow. setValue of (PASSIVE!E + grabbed row, new Date())
I did find a function online to find the first row that matched the ACTIVE!A with PASSIVE!A. But it kept overwriting the date on the first match. It never ignored row with nonempty cell to the next match row. Maybe I was just slightly off, which is why I'm asking for a lot of detail and explanation in the Answers.
This was the Code I used from another answer.
function getCurrentRow() {
var currentRow = SpreadsheetApp.getActiveSheet().getActiveSelection().getRowIndex();
return currentRow;
}
function onSearch1()
{
What I added
var row = getCurrentRow();
var activeLocation = getValue('ACTIVE!A' + row);
Continued Other Code
var searchString = activeLocation;
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("PASSIVE");
var column =1; //column Index
var columnValues = sheet.getRange(2, column, sheet.getLastRow()).getValues(); //1st is header row
var searchResult = columnValues.findIndex(searchString); //Row Index - 2
What I added
setValue(PASSIVE!E + searchResult, new Date().toLocaleString())
It worked if everyone has a different name, but the search Result always found the first row of the match, I tried adding an if ACTIVE!A == PASSIVE!A
&& PASSIVE!E =="", grabRow (I know this isn't proper code) But I didn't even know where to put this if function or if it would work or if it would just keep coming up false after it runs the first time true.
Continued Other Code
if(searchResult != -1)
{
//searchResult + 2 is row index.
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("PASSIVE").setActiveRange(sheet.getRange(searchResult + 2, 1))
}
if(searchResult = searchResult2) {
setValue('PASSIVE!E' + searchResult, new Date().toLocaleString())
}
}
Array.prototype.findIndex = function(search){
if(search == "") return false;
for (var i=0; i<this.length; i++)
if(this[i] == search) return i;
return -1;
}
So this is what I used, but not sure if it's the right way to go about this. Every time I used it, it would only set the SearchResult to the first row it found that had the searchString I'd actually prefer if it found the last row, considering the add record goes down over time and signing in should be the most recent name. But I'm guessing if I can just get a function that searches a range and finds the row for two values in specific columns, I can then just setValue('PASSIVE!E' + foundRow, new Date().toLocaleString())
Edit 5/9/2019 17:34 PST
Thank you to those Answering. I'm expanding on the question.
function rowWhereTwoColumnsEqual(value1,col1,value2,col2) {
var value1=value1 || 'A1';//testing
var value2=value2 || "";
The idea I'm having is to search Column1 of another sheet for, let's say, 'SheetA1' (the first sheet). And Column3 of another sheet for "" (cellisempty).
var value1= 'Sheet1!A1';
var value2= "";
var col1='Sheet2!A';
var col2='Sheet2!C';
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet2');
var rg=sh.getDataRange();
var vA=rg.getValues();
However, I don't know how the vA works. I also want to getRow() of the Row that is found in order to use that number in another function.
Try this:
function rowWhereTwoColumnsEqual(value1,col1,value2,col2) {
var value1=value1 || 9;//testing
var value2=value2 || 8;
var col1=col1 || 1;//testing
var col2=col2 || 2;//testing
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var rg=sh.getDataRange();
var vA=rg.getValues();
var rA=[];
for(var i=0;i<vA.length;i++) {
if(vA[i][col1-1]==value1 && vA[i][col2-1]==value2) {
rA.push(i+1);
}
}
SpreadsheetApp.getUi().alert(rA.join(','));
//return rA;//as an array
//return rA.join(',');//as a string
}