Replace text with hyperlinked version onEdit - function

I have a google sheet with a list of people's initials in range A4:A.
These initials are each hyperlinked to a different part of my sheet.
Column MM contains many cells that all have a data validation dropdown list referencing the initials list in A4:A
I need a script that triggers on edit so that when a user selects initials in the dropdown list in column MM, the script replaces it with the hyperlinked version from column A
My scripting knowledge is rudimentary. Normally I'd do a replace something like the below but I'm not sure how to implement the search so it takes the value from column MM and searches for it in columnA (or even if this would take the hyperlink function along with it.
`function replaceText(){
var oldText = "AB";
var newText = "AB hyperlinked";
var sheet = SpreadsheetApp.getActive().getSheetByName('SHOTS');
sheet.getRange("MM1:MM" + sheet.getLastRow()).createTextFinder(oldText).replaceAllWith(newText);
}`
Thanks in advance for any help given

If you have the chance of having an additional column, you can do a workaround without script with FILTER:
=FILTER(A2:A,A2:A=MM1)
Or if you want it as an array for many rows
=BYROW (MM1:MM,LAMBDA(each,IF(each="","",FILTER(A2:A,A2:A=each))))
Check in this image how when I chose value "b" it returns the hyperlinked value in the coloured column
OPTION B with SCRIPT:
Please check if MM is column 351, if not change that number. Run it once from the console (it will give an error, but it's just for approving the permissions), then go to your sheet and start trying with your dropdown:
function onEdit(e) {
var ecolumn = e.range.getColumn()
if (ecolumn == 351){
var sheet = SpreadsheetApp.getActive()
var range = sheet.getActiveRange();
var value = range.getValue()
var lookuprange = sheet.getRange("A:A").getValues().map(n => n[0])
var index = lookuprange.indexOf(value)+1
if (index != 0){
var link = sheet.getRange("A"+index).getRichTextValue().getLinkUrl()
var richValue = SpreadsheetApp.newRichTextValue()
.setText(value)
.setLinkUrl(link)
.build();
range.setRichTextValue(richValue);}}
}
REFERENCE: Apps Script: how to get hyperlink from a cell where there is no formula

Related

Google Sheets - Script to Change Currency Formatting Based On Dropdown Selection ($ + €)

I have a Sheet with two tabs — Tab 1 is a quote/invoice and Tab 2 is a list of clients. C3 on Tab 1 contains a dropdown list which pulls from Tab 2, Clients 1-4 use USD ($) but Client 5 uses EURO (€). Tab 1 also contains a costs column in I and a feeder column in H.
My goal is to use a script to change the currency formatting in column I based on what's selected in C3. When Client 5 is selected all costs in I need to contain a '€' prefix, and when anyone else is selected they need to contain a '$' prefix.
In the following code 'H' is a feeder cell where 'EURO' or 'literally anything else' can be entered to change the currency format in 'I' between '€' to '$':
function onEdit(e){
var sheetName = "Main Sheet";
var currencyCol = 8; //column H
var amountCol = 9; //column I
var defaultFormat = "[$$]#,##0.00";
var currencyFormat = {"USD":"[$$]#,##0.00",
"EURO":"[$€]#,##0.00"};
var r = e.range;
if(e.source.getSheetName()==sheetName && r.getColumn() == currencyCol){ // This assumes I want to manually change H, I'd rather have it automatically change between EURO (including € in column I) when client 5 is selected, and USD (including $ in column I) when clients 1-4 are selected
var uf = currencyFormat[r.getValue()];
uf = uf?uf:defaultFormat;
r.offset(0,amountCol-currencyCol).setNumberFormat(uf);
}
}
It works, but I have to manually type 'EURO' or 'anything else' in 'H' line-by-line to change the currencies for 'I' - but I need it to change automatically based on the selection in C3.
I tried using =if(C3="Client 5", "EURO", "") in Column H which works the first time Client 5 is selected in the dropdown, but doesn't reset 'I' to '$' once changed and needs to be manually typed in before working again.
I also tried 2 Macros which manually input 'EURO' and 'USD' into column H and tried to run them with a script to trigger when Client 5 is selected. It worked in 'H', but 'I' didn't change when the words appeared I think the issue is that the code isn't/can't trigger outside of manual data entry.
Any help would be great, I'm a beginner and I can't wrap my head around it. Feel free to message me or ask for a link to a test sheet.
Also I attached a screenshot of what tab 1 & 2 look like. I manually changed EURO to USD on Tab 1 to set that currency in column I, ideally that change can be based off C3Spreadsheet example
I believe your goal is as follows.
When a dropdown list of "C3" of "Tab1" sheet is changed, you want to change the number format of the cells "I8:I".
You want to retrieve the values of the dropdown list and the corresponding number formats from the cells "B4:C8" of "Tab2".
In this case, how about the following modification?
Modified script:
function onEdit(e) {
var sheetName1 = "Tab1"; // Please set the sheet name of "Tab1".
var sheetName2 = "Tab2"; // Please set the sheet name of "Tab2".
var r = e.range;
if (e.source.getSheetName() == sheetName1 && r.getA1Notation() == "C3") {
var defaultFormat = "[$$]#,##0.00";
var currencyFormat = { "DOLLAR": "[$$]#,##0.00", "EURO": "[$€]#,##0.00" };
var obj = e.source.getSheetByName(sheetName2).getRange("B2:C8").getValues().reduce((o, [b, c]) => (o[b] = c, o), {});
var sheet = r.getSheet();
sheet.getRange("I8:I" + sheet.getLastRow()).setNumberFormat(currencyFormat[obj[e.value]] || defaultFormat);
}
}
Note:
In this modified script, when the dropdown list of "C3" of "Tab1" sheet is changed, the number format of cells "I8:I" is changed by the values of "B4:C8" of "Tab2". In this case, the column "H" is not used. Please be careful about this.
In your script, "USD":"[$$]#,##0.00" and "EURO":"[$€]#,##0.00" are used. But, in your image, "DOLLAR" and "EURO" are used. In this modification, "DOLLAR" and "EURO" are used. Please be careful about this.
Reference:
reduce()

How can I conditionally copy between Google sheets?

I would like to conditional copy a value from one sheet to another.
About the below script:
It checks if in SOURCE tab the value in column H is equal to 45 and if Column A is empty. If so, it should copy the value in Column G into the last row of column A in TARGET tab. It also ads data to some of the TARGET tab columns
If I change this: sourceSheet.getRange(rangeToCopy).copyTo(destination.getRange(destination.getLastRow()+1, 1)),{contentsOnly:true};
to this: sourceSheet.getRange(rangeToCopy).copyTo(destination.getRange(destination.getLastRow()-1, 1)),{contentsOnly:true}; the cell is pasted on Column A on the penultimate row, which makes me think that the present code tries to copy to a row after the last visible one instead of pasting into the next blank cell after a non blank one in Column A, hence returning the error explained on the next bullet
As is, it returns the error: The coordinates of the range are outside the dimensions of the sheet. (I understood that the problem is because of the use of getlasrow and being used as is, it's trying to retrieve a row that does not exist)
This is the code so far:
function CopyTo(){
var sheet = SpreadsheetApp.getActive();
var sourceSheet = sheet.getSheetByName("SOURCE");
var destination = sheet.getSheetByName("TARGET");
var lastRow = sourceSheet.getDataRange().getLastRow();
for(var row=3; row<=lastRow; row++){
if(sourceSheet.getRange(row,1).getValue() == "" & sourceSheet.getRange(row,8).getValue() >= "45"){
Logger.log("CELL H"+row+" has \">=45\""+"\n=================\n"+"CELL A"+row+" is \"empty\""+"\n=================\n"+"RANGE TO COPY:\n"+ "\"G"+row+":G"+row+"\"");
var rangeToCopy = "G"+row+":G"+row;
sourceSheet.getRange(rangeToCopy).copyTo(destination.getRange(destination.getLastRow()+1, 1)),{contentsOnly:true};
destination.getRange(destination.getLastRow(), 9).setValue("Added Text Column 9");
destination.getRange(destination.getLastRow(), 13).setValue("Added Text Column 13");
destination.getRange(destination.getLastRow(), 14).setValue(new Date());
}
}
}
Also, there are other columns in Target sheet that fetch values so maybe that's the reason why the script isn't pasting on the right place but I can't get it to work.
Could you be so kind to help out?
Thank you in advance.
This is a test sheet which returns the previously mentioned error when running the script.
UPDATE
I've added to the script:
var column = destination.getRange('A' + destination.getMaxRows())
var lastFilledRow = column.getNextDataCell(SpreadsheetApp.Direction.UP).getA1Notation().slice(1)
Logger.log(lastFilledRow);
so I would get the last filled row number in Column A. It retrieves it but I'm having trouble in using it as range and I get the error: Exception: Cannot convert 'Range' to int.
sourceSheet.getRange(rangeToCopy).copyTo(destination.getRange(destination.getRange(lastFilledRow,1), 1)),{contentsOnly:true};
The problem lies with your TARGET sheet having a set maximum row of 84, thus, destination.getLastRow()+1 will return 85 which is outside the said maximum row.
To solve this, create a new TARGET sheet or find a way to remove the limit in the current TARGET sheet. Your current code should be fine.
EDIT:
I've edited your v2 code to make it work on the current test sheet provided. Please see code below.
function CopyTo_V2(){
var sheet = SpreadsheetApp.getActive();
var sourceSheet = sheet.getSheetByName("SOURCE");
var destination = sheet.getSheetByName("TARGET");
var lastRow = sourceSheet.getDataRange().getLastRow();
var column = destination.getRange('A' + destination.getMaxRows())
for(var row=3; row<=lastRow; row++){
if(sourceSheet.getRange(row,1).getValue() == "" & sourceSheet.getRange(row,8).getValue() >= "45"){
Logger.log("CELL H"+row+" has \">=45\""+"\n=================\n"+"CELL A"+row+" is \"empty\""+"\n=================\n"+"RANGE TO COPY:\n"+ "\"G"+row+":G"+row+"\"");
var rangeToCopy = "G"+row+":G"+row;
var lastFilledRow = parseInt(column.getNextDataCell(SpreadsheetApp.Direction.UP).getA1Notation().slice(1))
Logger.log(lastFilledRow);
sourceSheet.getRange(rangeToCopy).copyTo(destination.getRange(lastFilledRow+1,1)),{contentsOnly:true};
destination.getRange(lastFilledRow+1, 9).setValue("Added Text Column 9");
destination.getRange(lastFilledRow+1, 13).setValue("Added Text Column 13");
destination.getRange(lastFilledRow+1, 14).setValue(new Date());
}
}
}

Google Sheets Apps Script : use checkbox to copy cell content to another cell

I would like to create a function to copy cell content, within the same sheet, from two columns (A & C) to columns E & G (respectively), if a checkbox in column D is checked.
Same question if it were instead for a range of columns (A-C) being copied to columns (E-G) with checkbox still in column D.
Thank you for your helpful and insightful solution(s).
Solution
You can try using this sample script below that uses the copyTo() method inside an onEdit() trigger with a condition to only copy when a checkbox on column D is selected.
Sample Script
function onEdit(){
var ss = SpreadsheetApp.getActive().getActiveSheet();
//Gets the Location where user cliks on the sheet
var selection = ss.getActiveCell().getA1Notation().split("");
var row = ss.getActiveCell().getRow();
//Gets the checkbox value
var checkBoxValue = ss.getActiveCell().getValue().toString();
//Makes sure to run only when user selects any checkbox on column D
if(selection[0] != "D") return;
switch(checkBoxValue){
case checkBoxValue = "true":
ss.getRange("A"+row).copyTo(ss.getRange("E"+row));
ss.getRange("C"+row).copyTo(ss.getRange("G"+row));
/* if you want to change it to copy a range of columns, use this instead:
ss.getRange("A"+row+":C"+row).copyTo(ss.getRange("E"+row+":G"+row);
*/
break;
}
}
Test Demonstration
Sample Sheet

Send email notification when cell has been edited on a specific google sheets tab

I'm completely new to writing app scripts. Have been trying to find an answer to my question online but unfortunately without any success. Therefore I'm reaching out to the community for help.
In my g sheet, I have two tabs one is an input tab (X) and the other one is an output (Y). What I am trying to accomplish is to send an email notification to a given email address every time a cell has been edited in the output tab (Y). The output tab has an editable range from B2:Y61. I have found plenty of stuff online on the "on edit" feature. However, non of them was close to my example mostly because they have been referring to getActivetSheet and not a specific one. The simpler code the better.
For instance, I found the following post very interesting. Unfortunately have no idea how to make it work with a specific tab name. Furthermore, if copied and pasted exactly, the formula returns an error, that row reference has not been declared.
https://spreadsheet.dev/send-email-when-google-sheet-is-edited
Look forward to hearing from you!
Best,
D
You can use this:
function onEdit(e) {
var editedSheet = e.source.getActiveSheet();
var editedCell = e.range;
var row = editedCell.getRow();
var col = editedCell.getColumn();
//Check if B2:Y61 was modified in Sheet "Y" based on its row and column index
if(row>=2 && row<=61 && col>=2 && col<=25 && editedSheet.getName()=="Y"){
//Send email
var subject = "Sheet: "+editedSheet.getName();
var message = "Cell "+editedCell.getA1Notation()+" was modified from '"+e.oldvalue+"' to '"+e.value+"'";
MailApp.sendEmail("email address",subject,message);
}
}
What it does?
Using the Google Sheets events, you can get the active sheet that is being modified using e.source, get the cell being modified using e.range and even get the old and the new value of the cell using e.oldValue and e.value
You just need to include a condition to check if the cell modified is within the range that you prefer B2:Y61 which should have a min row = 2, max row = 61, min col = 2 and max col = 25 and sheet name should be "Y".
Send an email using MailApp.sendEmail() if all the conditions were met.
Note:
You need to create this as an installable trigger since it will use mail service which requires authentication.
To manually create an installable trigger in the script editor, follow these steps:
OUTPUT:
(UPDATE)
This code will copy changes done in X_input sheet to Y_input sheet and check if email should be sent when B2:Y61 range was modified.
function onEdit(e) {
var editedSheet = e.source.getActiveSheet();
var editedCell = e.range;
var row = editedCell.getRow();
var col = editedCell.getColumn();
var ySheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Y_output");
//Check if edit is done in Sheet "X_input"
if(editedSheet.getName()=="X_input"){
//copy modified cell to Y_output sheet
ySheet.getRange(row,col).setValue(e.value);
//check if modified cell is within B2:Y61 range and send an email
if(row>=2 && row<=61 && col>=2 && col<=25){
//Send email
var subject = "Sheet: "+editedSheet.getName();
var message = "Cell "+editedCell.getA1Notation()+" was modified from '"+e.oldvalue+"' to '"+e.value+"'";
MailApp.sendEmail("ronoel#google.com",subject,message);
}
}
}

Copying Data Sheet1 to Sheet2 so you can sort & edit both sheets (google apps script?)

I am working in goggle sheets and think I need to use a google apps script to do what I want, but I am a psychologist at a non-profit University hospital trying to do some good and not a programmer (which probably shows) and I am desperately in need of help. I am trying to set up a series of spreadsheets to track participation in workshops for our treatment method.
1) I have a sheet “Participant_Registration” where basic information is entered
2) I want to transfer information from only the first four columns (A:D) of “Participant_Registration” to a second sheet “Learning_Sessions_Attendance”
3) I am also transferring the same information to a third sheet 'Consultation1_Attendance' – but I need to first filter and select only those people assigned to that group.
Here is a link to a copy of my spreadsheet.
https://docs.google.com/spreadsheets/d/17d0bT4LZOx5cyjSUHPRFgEZTz4y1yEL_tO3gtSJ4UJ8/edit?usp=sharing
More generically this is what I am trying to do. Is this possible in google app scripts? It seems it should be.
1) I have original data in sheet1
2) I want the first four columns (A:D) to transfer to sheet2 (it is fine if I need a trigger variable)
3) I want them to transfer in such a way that if you sort either sheet, the data are still fine (still linked to the right line).
4) Ideally if there is a change to the data in the source sheet (Sheet1) the same change will be made in Sheet2.
5) Ideally this would all happen automatically without human intervention through a script.
Any ideas?? I so need your help. I have been all over the forum, git hub, and done a ton of searches and tried following a lot of examples I saw but nothing works. I really need help.
Here are my sample scripts each with a problem:
//The following code copies a range from sheet1 to sheet2 as I wanted. A problem occurs if after if we copy the data from sheet1 we add data to other columns on sheet2. Later if we sort on some variable (which people are bound to do) if the function is deployed again it will overwrite data meaning the data from sheet1 are not connected to the right individual on sheet2
function CopyRange() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Participant_Registration");
var range = sheet.getRange("A14:D");
var values = range.getValues();
var target = ss.getSheetByName("Learning_Sessions_Attendance");
var target_range = target.getRange("A10:D");
range.copyTo(target_range);
}
So I tried again. This time I tried to just copy the last edited row from sheet1 to sheet2. This function does not appear to work for me.
function CopyRow2() {
// Get Spreadsheets
var source = SpreadsheetApp.openById("1egn6pnRd6mKMGuQxX_jtgwYDtkuMUv2QJItLdh7aIEs");
var target = SpreadsheetApp.openById("1egn6pnRd6mKMGuQxX_jtgwYDtkuMUv2QJItLdh7aIEs");
// Set Sheets
var source_sheet = source.getSheetByName("Participant_Registration");
var target_sheet = target.getSheetByName("Learning_Sessions_Attendance");
var rowIdx = source_sheet.getActiveRange().getRowIndex();
var rowValues = source_sheet.getRange(rowIdx,1,1,source_sheet.getLastRow()).getValues();
Logger.log(rowValues);
var destValues = [];
destValues.push(rowValues[0][0]);// copy data from col A to col A
destValues.push(rowValues[0][1]);//copy data from col B to col B
destValues.push(rowValues[0][2]);//copy data from col C to col C
destValues.push(rowValues[0][3]);//copy data from col D to col D
var dest=source.getSheets()[4];
dest.getRange(dest.getLastRow()+1,1,1,destValues.length).setValues([destValues]);//update destination sheet with selected values in the right order, the brackets are there to build the 2D array needed to write to a range
}
So I tried again and again. I have lots of examples but none seem to work.
Thanks so much.
Chandra
For that to happen automatically (one sheet's change updating another sheet), you will surely need an "event/trigger" to run a script whenever you change a cell. (that is the "onEdit()" function).
But since scripts are likely to fail sometimes (even when they are perfect, that's because of some Google issues), it's not guaranteed that the sheets will always contain the same data.
But, if I could suggest another way, do not let ID be optional. If that is a real ID (like the person ID card number), create another ID exclusively for working with the sheet.
I have edited your second sheet showing a suggestion of how to do it without using scripts. The only things you must be aware of are:
Do not create two people with the same ID.
You have to insert (only) the ID manually in the second sheet.
The VLOOKUP forumla will search for that ID in the first sheet and return the data in the same line. You can sort any sheet in whatever way you like. As long as you don't change people's IDs.
So, in sheet 2, use this in the First Name, Last Name and Email address:
=vlookup(A10,Participant_Registration!$A:$D,2,false)
=vlookup(A10,Participant_Registration!$A:$D,3,false)
=vlookup(A10,Participant_Registration!$A:$D,4,false)
Just extend this formula downwards
I hope this helps. I would avoid scripting for that at any cost. It would be my last resort. (Scripts also need to be changed if you want to rearrange your sheet, and if not, they might cause trouble, write over existing data...)
I also added a button (insert - drawing) and put a script in it (right button, click down arrow, "transfer? script" -- translated from Portuguese).
If you lock all four columns in sheet2 and lock the ID column in sheet 1, people will not be able to chang IDs and cause mess. They can edit people in sheet 1 and not change the formula in sheet2. Script is not affected by sorting or empty spaces (it adds the person in the first empty row it finds).
I added "named ranges" for the four column headers. (With named ranges, the script can refer to names instead of coordinates, which enables you to rearrange the sheet inserting and deleting columns, or moving them with CUT and paste - but the VLOOKUP formula will need manual update if you rearrange columns).
Here is the code: (it could get better if you manage to create dialog boxes and ask for the person's data inside that dialog, then you could lock everything - and you would need an edit button besides the add).
function AddPerson()
{
var S1Name = "Participant_Registration";
var S2Name = "Learning_Sessions_Attendance";
var ID1Name = "regID";
var ID2Name = "learnID";
//these vars are not used in this script
var FN1Name = "regFirstName";
var FN2Name = "learnFirstName";
var LN1Name = "regLastName";
var LN2Name = "learnLastName";
var Email1Name = "regEmail";
var Email2Name = "learnEmail";
var sSheet = SpreadsheetApp.getActiveSpreadsheet();
var Sheet1 = sSheet.getSheetByName(S1Name);
var Sheet2 = sSheet.getSheetByName(S2Name);
var ID1 = getRangeByName(sSheet, Sheet1.getName(), ID1Name);
var ID2 = getRangeByName(sSheet, Sheet2.getName(), ID2Name); Logger.log("ID2: " + ID2.getValue());
var Empty1 = getFirstEmpty(ID1);
var Empty2 = getFirstEmpty(ID2);
var Biggest1 = getBiggestID(ID1); Logger.log("Biggest 1: " + Biggest1);
var Biggest2 = getBiggestID(ID2); Logger.log("Biggest 2: " + Biggest2);
if (Biggest1 !== Biggest2)
Browser.msgBox("Warning: there are IDs in one sheet that are not in the other sheet");
var Biggest;
if (Biggest1 > Biggest2) Biggest = Biggest1;
else Biggest = Biggest2;
Biggest++;
Empty1.setValue(Biggest);
Empty2.setValue(Biggest);
}
function getFirstEmpty(Header)
{
while (Header.getValue() !== "")
{
Header = Header.offset(1,0);
}
return Header;
}
function getBiggestID(Header)
{
var Sheet = Header.getSheet();
var LastRow = Sheet.getLastRow();
var Values = Sheet.getRange(Header.getRow(), Header.getColumn(), LastRow - Header.getRow() + 1).getValues();
var len = Values.length;
var MaxID = 1;
for (var i = 0; i < len; i++)
{
var val = Number(Values[i]);
if (!isNaN(val) && val > MaxID)
MaxID = val;
}
return MaxID;
}
function getRangeByName(spreadSheet, sheetName, rangeName)
{
Logger.log("Trying range: " + "'" + sheetName + "'!" + rangeName);
return spreadSheet.getRangeByName("'" + sheetName + "'!" + rangeName);
}