I'm using the current Google Spreadsheets. NOT the "new" version of Sheets that's in Beta at the moment. I want to do 2 things:
1 - Receive an email reminder when a certain date is reached in a cell. There is a script for this in the Scripts Gallery called "Add Reminder" but it seems to have problems (you can't set a different reminder for each tab in a spreadsheet and this is crucial functionality)
2 - Change the color of an 'entire row' automatically triggered by a cell's content. I already used the conditional formatting to try to do this, but it can only change the color of the single cell, not the entire row
I don't know how to write scripts and I know there are lots of scripts people have posted online that address these issues, but I'd looking for reliable and fast. Hoping that someone with experience can guide me to the right source for this.
Thanks!
Scott
you can set a different reminder for each tab by just repeating the code...
function remindMe() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var data = sheet.getDataRange().getValues();
for(var i = 1; i < data.length; i++){
if(data[i][2] > new Date()){
MailApp.sendEmail(message);
}
}
// Now for tab index [1]
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];
var data = sheet.getDataRange().getValues();
for(var i = 1; i < data.length; i++){
if(data[i][2] > new Date()){
MailApp.sendEmail(message);
}
}
}
To set an entire row color in the old Sheets use some thing like...
sheet.getRange('1:1').setBackground('yellow');
Related
I'm working on a spreadsheet where data is added in real time by an API. The data from the API is from users who sign up for a newsletter consists of basic data. When data is send from de API, it is added as a new row in the spreadsheet.
Users also have the option to answer additional newsletter questions later, this will also cause the API to add a new row, with additional data that is placed in different columns, but also still show the existing data that was previously known.
To avoid clutter, I want to remove duplicates based on one column and keep the last entry in Google Sheets. Which results in removing the old basic data row and only keeping the row with additional data. To highlight that this is data that is "updated" by the user, I also highlight this row. The data used to mark submissions as duplicates will be based on a user's email address. Since this will remain the same in both cases. [I may have to be careful with uppercase and lowercase letters, where the script doesn't see two emails as duplicates, I don't have an answer for that yet]
Besides this I already have a script in place that adds current time and date to an added row and places it in the first colomn.
For the duplicate issue, I already found a simular question Remove duplicates based on one column and keep latest entry in google sheets and the solution from Tanaike was very helpfull. Overall this code works for me, but sometimes it seems that the script runs when it's not suppose to.
My current script looks like this:
function onChange(e){
if (e.changeType == 'INSERT_ROW'){
const sh = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sh.getRange(sh.getActiveRange().getRow(), 1).setValue(new Date());
}
}
function removeDuplicates() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Inzendingen');
var dt = sh.getDataRange().getValues();
var uA = [];
for (var i = dt.length - 1; i >= 0; i--) {
if (uA.indexOf(dt[i][4]) == -1) {
uA.push(dt[i][4]);
} else {
sh.deleteRow(i + 1);
Utilities.sleep(500);
sh.getRange(sh.getLastRow(),1,1,sh.getLastColumn()).setBackground('lightblue');
}
}
}
I added Utilities.sleep(500); to prevent the scenario where the row that's being deleted was faster than the highlight. So to prevent having an empty highlighted row at the bottom underneath the latest entered row.
Both scripts are setup with the trigger: From Spreadsheet - On Change
If everything works as planned, it should work something like this (all fake data, no worries):
My problem is as follows:
Currently some new users that are being added by the API for the first time, are also being highlighted. I suspect this has something to do with the fact that the duplicate deletion also works when the value of the email colomn is empty. However, this is only an assumption given the limited knowledge I have of these matters.
Seen is this example:
Long story short
I would love for this script to work as I intend it to do, where it only removes duplicates based on a duplicate email adress in colomn E. It would be even better if the duplicate deletion script also ignores capitalization. And lastly that it also ignores blank entries in colomn E.
I tried to use the script Remove duplicates based on one column and keep latest entry in google sheets
And make some addition in this script. Stuff like:
function removeDuplicates() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Inzendingen');
var dt = sh.getDataRange().getValues();
var uA = [];
for (var i = dt.length - 1; i >= 0; i--) {
if (uA.indexOf(dt[i][4]) == -1 && dt.length !=0 ) {
uA.push(dt[i][4]);
} else {
sh.deleteRow(i + 1);
sh.getRange(sh.getLastRow(),1,1,sh.getLastColumn()).setBackground('lightblue');
}
}
}
Where I thought adding && dt.length !=0 would signal to the "if" to only trigger when there's a duplicate and when the value/length is not 0.
If I understand you correctly, the only issue is around these new people with no emails being highlighted. I believe you are on the right track, but you have dt.length != 0, which is looking at the entire array. Instead, you want to just check for the email.
As such, you can use this:
dt[i][4].length != 0
or
dt[i][4] != ""
EDIT:
I believe this will give the results you want. Blank emails are ignored, and duplicate emails ignore case.
function removeDuplicates() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Inzendingen');
var dtAll = sh.getDataRange().getValues();
dt = dtAll.map(function(f){return [f[4].toLowerCase()]});
var uA = [];
for (var i = dt.length - 1; i >= 0; i--) {
if (uA.indexOf(dt[i][0]) == -1) {
uA.push(dt[i][0]);
Logger.log(uA[i]);
} else if (dt[i][0] != ""){
sh.deleteRow(i + 1);
sh.getRange(sh.getLastRow(),1,1,sh.getLastColumn()).setBackground('lightblue');
}
}
}
Some time ago, Google Sheets changed the adding of links to rich text, and now links cannot be found at the formula anymore. Personally I dislike this change very much because I use a custom function that extracts hyperlinks from cells with the old formula, and with the new change I just cannot find a way of doing this. I am not very good with functions yet, mainly the reason why I wrote my question title as detailed as possible.
What I need to do is to extract hyperlinks from cells using a custom formula, since it will need to be used among many other vanilla formulas. How can I set up a custom function/formula to extract the new hyperlinks based on range?
Here are the sheets where I want to extract links:
https://docs.google.com/spreadsheets/d/1JnSKQ7nd4J3NPRH4uSsOCYms-DF16j1pkCAuJeikToo/edit#gid=317867416
I would like to extract links from the games that are being posted, because I need to use those links elsewhere and I'd also like to have them ready to be imported if ever needed.
I need to specify a formula in another cell which will extract those links. For example =GETURL(B6) which would extract the new rich text hyperlinks based on a range that I insert for it.
Alternatively, is it possible to configure the document so that it makes links in the old format whenever inserted? This way I could try to workaround the new settings and future inserted links would go into the =HYPERLINK formula instead.
Many thanks!
I think this script would come in handy.
It gives the possibility to retrieve back the URL from a hyperlink formula.
Go to script editor, and create a new project.
Save the file.
Head up to Run > linkURL to run the script. This will create a new function in Sheets.
Let’s say cell A1 has the hyperlink in it. Go to any cell and type =linkURL(A1), and then hit Enter.
function linkURL(reference) {
var sheet = SpreadsheetApp.getActiveSheet();
var formula = SpreadsheetApp.getActiveRange().getFormula();
var args = formula.match(/=w+((.*))/i);
try {
var range = sheet.getRange(args[1]);
}
catch(e) {
throw new Error(args[1] + ' is not a valid range');
}
var formulas = range.getFormulas();
var output = [];
for (var i = 0; i < formulas.length; i++) {
var row = [];
for (var j = 0; j < formulas[0].length; j++) {
var url = formulas[i][j].match(/=hyperlink("([^"]+)"/i);
row.push(url ? url[1] : '');
}
output.push(row);
}
return output
}
OK, still rookie coder but getting better. Able to modify codes but not write most of them myself yet. Here is my current problem.
I run a small business and we use google sheets as our CRM (its faster and easier for us to do it this way) I have a master sheet that I bring in with =importrange everyone else jobs. It works perfect, makes my life real easy but there is one thing I can not get to come over. That is the notes stored in a cell. I have to actually open their sheet to view the notes. So I am trying to get a script that would update the notes down the importedrange. Then each day when I go over their info I push a button and it would write the notes from the other persons sheet onto my sheet, can just write over the last note and replace it.
I made an example with 3 sheets (all made editable for everyone) since I can't post our actual business sheets to work with. I should be able to modify it and transfer to my actual sheets after some help.
(Master Sheet) https://docs.google.com/spreadsheets/d/1TMNyohd5Vtn3p9cpLebmZASt2TzbWL80fIesCu89-ig/edit?usp=sharing
(Employee 1)
https://docs.google.com/spreadsheets/d/1n4iFXGuC7yG1XC-UIbuT9VrQ7rJWngPkDCv0vsvDed4/edit?usp=sharing
(Employee 2)
https://docs.google.com/spreadsheets/d/1EJVa5TgF6UkLhiLtfQ6o7BzpdzXDGcVhkibLCYwlfAU/edit?usp=sharing
Links are provided to each other sheet on the top of the master sheet. This is above my head so I won't try to butcher the code below lol. Here is a function I found but don't know how to implement it to use with import range.
function getNotes(rangeAddress) {
// returns notes inserted with Insert > Note from one cell or a range of cells
// usage:
// =getNotes("A1"; GoogleClock())
// =getNotes("A1:B5"; GoogleClock())
// see /docs/forum/AAAABuH1jm0xgeLRpFPzqc/discussion
var ss = SpreadsheetApp.getActiveSpreadsheet();
var range = ss.getRangeByName(rangeAddress);
if (!range) return '#N/A: invalid range. Usage: =getNotes("A1:B5"; GoogleClock())';
var notesArray = new Array();
for (var i = 0; i < range.getHeight(); i++) {
notesArray[i] = new Array();
for (var j = 0; j < range.getWidth(); j++) {
notesArray[i][j] = range.getCell(i + 1, j + 1).getComment();
}
}
return notesArray;
}
So The code I would want it to read the note from "Employee 1" sheet and write it onto the "Master" sheet in the correct cell. Since it is an =importrange the orientation of the cells will always be the same on both sheets, just needs to pick the starting cell and go down the list. I want to make it work with the button I put on the top of the master sheet on each tab.
This script uses outdated methods. For example, GoogleClock() is gone.
Please see my other response to your similar question for a possible solution.
I've made a list of events in Google Sheets to help me and my band to get an overview of when we are to play. We have had a lot of jobs lately, and it is getting a little hard to quickly figure out where the next job is...
If it's possible, I would like to automatically make the rows containing previous jobs in italics, line-through and the font-color gray if the dates i column C is before the today/current date.
I have been searching a lot for it, but haven't really quite found the right piece of code..
My skills in scripting is OK, but this is the first time for me to use Google apps script, so I would appreciate if you could explain some of the harder steps about the more Google Sheets specific parts of the code :)
Thanks a lot!
You don't need a script to do that : in your spreadsheet, format>conditional formating.
EDIT :
Since you seem to prefer using a script to customize it more personally (which I understand ;-)
here is a script to begin with : (it does not need a lot of explanations, see comments in code)
function formatOnDate() {
var sh = SpreadsheetApp.getActive().getActiveSheet();
var range = sh.getDataRange();
var data = range.getValues();
var color = '#CCC';// value you want
var style = 'italic';// value you want
var line = 'line-through';// value you want
var fontColors = range.getFontColors();// get all font colors
var fontLines = range.getFontLines();// lines
var fontStyles = range.getFontStyles();//style
//var today = new Date();// include today in sheet
var today = new Date(new Date().setDate(new Date().getDate()-1));// exclude today... uncomment the one you use
for(var n=1 ; n<data.length ; n++){ // start on row 2 so that headers are not changed
if(data[n][2] < today){
for(var c in data[0]){
fontColors[n][c]=color;//set format
fontLines[n][c]=line;//set format
fontStyles[n][c]=style;//set format
}
}
}
sh.getRange(1,1,data.length,data[0].length).clear();
// now update sheet with new data and style
sh.getRange(1,1,data.length,data[0].length).setValues(data).setFontColors(fontColors).setFontLines(fontLines).setFontStyles(fontStyles);
}
Test sheet here
I have been trying to get this to work for a couple of days now and I give up.
I want to create a Google form with a drop down list populated form a spreadsheet. I don't what anyone to choose the same as any one else. (like in a potluck situation)
example:
I am giving away :
a comb
a brush
a bowl full of mush
I tell Thomas, Richard and Henry that they can each have one and send them a link to a Google form I created. Tom is quick and opens the form 1st. He enters his name and chooses a comb out of a three item drop down list. Dick opens the form link and in the same drop down question he chooses out of the two remaining items. He chooses the brush. Harry is a bit of a slow poke, so when he gets home he opens my link, but alas, he can only have a bowl full of mush.
How can I get this done?
Based on my research so far I will be needing to use the if function on the responses spread sheet to see if there has been a take for an item (see if the cell is vacant) and maybe VLOOKUP, but I can't get a clear picture of how to make it all work together.
Thank you,
Good night
EDIT:
Based on gssi's answer, I wanted to post the code and describe the way I did it.
function updateListChoices(item){
var inventory = (SpreadsheetApp.openById(theIdOfTheResponceSpreadsheet)
.getSheetByName("inventory")
.getDataRange()
.getValues());
var selected = (SpreadsheetApp.openById("0Al-3LXunCqgodHB5RGNpR0RyQ0pERmVnek1JeUJKS0E")
.getSheetByName("responses")
.getDataRange()
.getValues());
var choices = [];
var selectedReal = [];
for (var i = 0; i< selected.length; i+=1){
selectedReal.push(selected[i][2]) }
for (var i = 1; i< inventory.length; i+=1){
if(selectedReal.indexOf(inventory[i][0])=== -1){
choices.push(item.createChoice(inventory[i][0]));}
}
item.setChoices(choices);
}
var LIST_DATA = [{title:"the title of the question", sheet:"inventory"}]
function updateLists() {
var form = FormApp.getActiveForm();
var items = form.getItems();
for (var i = 0; i < items.length; i += 1){
for (var j = 0; j < LIST_DATA.length; j+=1) {
var item = items[i]
if (item.getIndex() === 1){
updateListChoices(item.asListItem(), "inventory");
break;
}
}
}
}
In the building of the form, click the tools menu, then click script editor. Copy the code from here (with changes to fit your needs) to the script editor and hit save. Click the Resources menu and hit the project triggers (the 1st option). Click Add trigger. Choose updateLists from form do this once with when sending and once when opening (you should end up with 2 lines.)
It isn't very elegant, but this is what I am capable of. Good Luck.
I tried to accomplish exactly the same (list with products to select from), but I couldn't make it work with your final code example. Here's mine, with detailed instructions. Just for anybody who is landing on this page and is looking for a working code.
(Menu names might differ from yours, because I'm using a non-English Google Forms, and I'm just guessing the translations here.)
1) Create a new form, and create a new radio button based question (multiple choice) (In this example, I use the question name: "Select a product"). Don't add any options to it. Save it.
2) Open the spreadsheet where the responses are going to be stored, and add a new sheet in it (name: "inventory")
3) Fix the first row (A) of the inventory sheet, and put in A1: "Select a product"
4) Put in column A all the products you want to appear in the form
5) Open the form editor again, and go to tools > script editor
6) Paste this code in the editor, put in your form and spreadsheet ID's (3x) and save it.
var LIST_DATA = [{title:"Select a product", sheet:"inventory"}];
function updateLists() {
//var form = FormApp.getActiveForm();
var form = FormApp.openById("paste_ID_of_your_FORM_here");
var items = form.getItems();
for (var i = 0; i < items.length; i += 1){
for (var j = 0; j < LIST_DATA.length; j+=1) {
var item = items[i];
if (item.getTitle() === LIST_DATA[0].title){
updateListChoices(item.asMultipleChoiceItem(), LIST_DATA[0].sheet);
break;
}
}
}
}
function updateListChoices(item, sheetName){
var inventory = (SpreadsheetApp.openById("paste_ID_of_your_RESPONSE_SHEET_here")
.getSheetByName("inventory")
.getDataRange()
.getValues());
var selected = (SpreadsheetApp.openById("paste_ID_of_your_RESPONSE_SHEET_here")
.getSheetByName("responses")
.getDataRange()
.getValues());
var choices = [];
var selectedReal = [];
for (var i = 0; i< selected.length; i+=1){
selectedReal.push(selected[i][1])
}
for (var i = 1; i< inventory.length; i+=1){
if(selectedReal.indexOf(inventory[i][0])=== -1){
choices.push(item.createChoice(inventory[i][0]));}
}
if (choices.length < 1) {
var form = FormApp.getActiveForm();
form.setAcceptingResponses(false);
} else {
item.setChoices(choices);
}
}
7) With the code editor open, go to Resources > Create triggers and create these two triggers. They need to appear in this order:
updateLists - from form - sending
updateLists - from form - opening
Now you're good to go. If you open the form editor, the products added in the inventory sheet will appear as options.
Every time a product is chosen, it will disappear from the form. To reset all the chosen products, go to the form editor, and choose Responses > Remove all responses. You might need to remove all responses from the responses sheet manually as well (Don't know why, but that happened to me). After that, you need to manually run the updateLists script in the code editor.
Here's a 'How-To' that describes how to construct the spreadsheet.
Column A : named 'Inventory', contains the names of the items initially available.
Column B : named 'Indices', in cell B1 contains the formula =if(isnumber(match(Inventory,Selected,0)),"",if(row(B1)=1,1,max(offset(Indices,0,0,row(B1)-1,1))+1)). Copy the formula in B1 into all cells below it in column B.
Column C : named 'Selected', contains the names of items currently selected starting in row 1 and continuing down contiguously.
Column D : named 'Available', cell D1 contains the formula =if(isnumber(match(row(D1),Indices,0)),index(Inventory,match(row(D1),Indices,0),1),"") which is then copied into all cells below it in column D.
The Available column will always contain a contiguous list of the 'as-yet-unselected' inventory items.