I've been trying to get a simple function to work for a couple of days but I can't see where I'm going wrong. I would really appreciate some help is possible please.
Background:
I have a calendar tab with a table containing Stores down the side and Dates across the top.
When the user clicks between E7:AI200 I want to be able to:
Grab the Store Name from column 1 and the clicked row and populate B1 with the value.
Grab the Date from row 1 and the clicked column and populate C1 with the value.
This seems to work fine.
I also want the spreadsheet to automatically navigate to another tab called 'Filtered Events' when the cell between E7:AI200 clicked.
This also seems to work fine.
Issue: When I click back to the Calendar tab it seems to re-click the original cell and navigates me straight back to the 'Filtered Events' tab and I am stuck in a loop of continously going between tabs each time I try to get back to the Calendar.
function onSelectionChange(e) {
var ss = SpreadsheetApp.getActive();
if (ss.getActiveSheet().getName() == "Calendar") {
var sheet = ss.getSheetByName("Calendar");
var full_range = sheet.getRange("A1:AI200");
var click_range = e.range;
var row_lookup = click_range.getRow();
var col_lookup = click_range.getColumn();
var selected_store = full_range.getCell(row_lookup, 1).getValue();
var selected_date = full_range.getCell(1, col_lookup).getValue();
var populate_store = sheet.getRange("B1");
var populate_date = sheet.getRange("C1");
if (row_lookup > 6 && row_lookup < 250 && col_lookup < 36 && col_lookup > 4) {
populate_store.setValue(selected_store);
populate_date.setValue(selected_date);
var change_sheet = ss.getSheetByName("Filtered Events");
change_sheet.activate();
}
else {
populate_store.setValue('');
populate_date.setValue('');
}
}
}
Probably you can use PropertiesService to store the last used coordinates of selected cell and check them at the start of the script:
function onSelectionChange(e) {
var ss = SpreadsheetApp.getActive();
if (ss.getActiveSheet().getName() == "Calendar") {
var sheet = ss.getSheetByName("Calendar");
var full_range = sheet.getRange("A1:AI200");
var click_range = e.range;
var row_lookup = click_range.getRow();
var col_lookup = click_range.getColumn();
// try to get the last used cell coordinates
var last_cell_row = PropertiesService.getScriptProperties().getProperty('last_cell_row');
var last_cell_col = PropertiesService.getScriptProperties().getProperty('last_cell_col');
// save current cell coordinates
PropertiesService.getScriptProperties().setProperty('last_cell_row', row_lookup);
PropertiesService.getScriptProperties().setProperty('last_cell_col', col_lookup);
// if the coordinates are the same break the function
if (row_lookup == last_cell_row && col_lookup == last_cell_col) return;
// the rest of your code ...
I haven't tested it. It would be better if you provide some dummy data for testing, how your sheets look like.
Related
I have a sheet where when I change a specific cell to "YES", I need a template sheet to be copied to a new version and named as per the value of a cell on the current row.
I'm having trouble working out how to get the value of the first cell in the row selected. This is what I have so far (I know this is wrong):
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var currentCell = sheet.getCurrentCell();
if (currentCell = "YES")
{
SpreadsheetApp.getActiveSpreadsheet().toast("New change control sheet added to workbook.","Change Control",15);
var sourceRow = ss.getActiveRange().getRowIndex();
var tabName = ss.getRange(cell,1).getValues();
ss.getSheetByName("CCTemplate").showSheet()
.activate();
ss.setActiveSheet(ss.getSheetByName('CCTemplate'), true);
ss.duplicateActiveSheet();
ss.setActiveSheet(ss.getSheetByName('CCTemplate'), true);
ss.getActiveSheet().hideSheet();
ss.setActiveSheet(ss.getSheetByName('Copy of CCTemplate'), true);
ss.getActiveSheet().setName("CC" & tabName);
}
}
Any ideas?
function onEdit(e) {
var sh=e.range.getSheet();
if(sh.getName()=='Your Sheet Name' && e.value=="YES") {
e.source.toast="New change control sheet added to workbook.","Change Control",15);
var tabName=sh.getRange(e.range.rowStart,1).getValue();
var tsh=e.source.getSheetByName('CCTemplate');
var csh=tsh.copyTo(e.source);
csh.setName('CC'+tabName);
}
}
You should avoid using activate in your scripts especially in simple triggers where you have to finish in 30 seconds. I think this code does the same thing that you intended for your code. One significant difference is that I use the information that comes in the event object that comes with the trigger. You should add the code Logger.log(JSON.stringify(e)) and then look at the logs you will see that there is a lot of information available to you which removes the need to run extra functions to get things like a spreadsheet.
Use event objects
onEdit offers among others the event objects range and value which are helpful to retrieve the range that has been edited and its value.
Also
When you want to a cell and compare it against a value, like in if (currentCell = "YES") - you need to retrive its value (either currentCell.getValue() or just event.value) and you need to use == instead of = for comparison.
Be careful with getValues() vs getValue(). The former gives you a 2D array and is not necessary if you want to retrieve the value of a single cell.
There is no need to set your sheet to active in order to change its name.
You can rewrite your code as following:
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var currentCell = event.range;
var value = event.value;
if (value == "YES")
{
...
var sourceRow = range.getRowIndex();
var tabName = ss.getRange(sourceRow, 1).getValue();
...
ss.getSheetByName('Copy of CCTemplate').setName("CC" + tabName);
}
}
After several googling, I arrived with a code that looks into a main sheet and shows or hides sheets (named A to N) depending on what checkbox is ticked/unticked (main requirement).
However, it seems to be not that efficient, and takes a couple of seconds to truly show/hide the sheets after ticking/unticking their respective checkboxes. I've recently learned that onEdit() always runs whenever there's an Edit anywhere in the spreadsheet.
I would like to seek advise if it is possible to only run onEdit() when edits are done only on specific ranges, specifically B6:B20 of Main sheet (which contains my checkboxes). Likewise, instead of going over all checkboxes and showing/hiding sheets based on what is ticked/unticked, is it possible for onEdit() to detect what was immediately ticked/unticked from among the checkboxes and show/hide the appropriate sheet? We maybe adding several other sheets in the future, and it will just get more inefficient if we try to scale the current code.
Here's the code:
function onEdit(e) {
var sheet = e.source.getSheetByName("Main");
var ss = SpreadsheetApp.getActiveSpreadsheet();
var A = ss.getSheetByName("A");
var B = ss.getSheetByName("B");
var C = ss.getSheetByName("C");
var D = ss.getSheetByName("D");
var E = ss.getSheetByName("E");
var F = ss.getSheetByName("F");
var G = ss.getSheetByName("G");
var H = ss.getSheetByName("H");
var I = ss.getSheetByName("I");
var J = ss.getSheetByName("J");
var K = ss.getSheetByName("K");
var L = ss.getSheetByName("L");
var M = ss.getSheetByName("M");
var N = ss.getSheetByName("N");
if(sheet.getRange('B6').getValue() === true){A.showSheet();}else{A.hideSheet();}
if(sheet.getRange('B7').getValue() === true){B.showSheet();}else{B.hideSheet();}
if(sheet.getRange('B8').getValue() === true){C.showSheet();}else{C.hideSheet();}
if(sheet.getRange('B9').getValue() === true){D.showSheet();}else{D.hideSheet();}
if(sheet.getRange('B10').getValue() === true){E.showSheet();}else{E.hideSheet();}
if(sheet.getRange('B11').getValue() === true){F.showSheet();}else{F.hideSheet();}
if(sheet.getRange('B12').getValue() === true){G.showSheet();}else{G.hideSheet();}
if(sheet.getRange('B13').getValue() === true){H.showSheet();}else{H.hideSheet();}
if(sheet.getRange('B14').getValue() === true){I.showSheet();}else{I.hideSheet();}
if(sheet.getRange('B15').getValue() === true){J.showSheet();}else{J.hideSheet();}
if(sheet.getRange('B17').getValue() === true){K.showSheet();}else{K.hideSheet();}
if(sheet.getRange('B18').getValue() === true){L.showSheet();}else{L.hideSheet();}
if(sheet.getRange('B19').getValue() === true){M.showSheet();}else{M.hideSheet();}
if(sheet.getRange('B20').getValue() === true){N.showSheet();}else{N.hideSheet();}
}
Likewise, here's a preview of our Main sheet. Based on the image, sheets B, E, J and K should be showing while the others should be hidden:
onEdit will fire anytime the sheet is edited, however, you can reference the event object (e) to determine which cell was modified, and only take action if the edit is on a cell you actually care about.
I would do this by creating a map of cells to sheets, so you don't need a bunch of if statements, but rather just check if the cell is mapped and take action if it is.
var actionable_cell_map = {'B6':'A',
'B7':'B',
...
'B20':'N'
};
function onEdit(e){
if(e.range.getSheet().getName() == "Main"){
var edited_cell = e.range.getA1Notation();
if(actionable_cell_map[edited_cell]){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(actionable_cell_map[edited_cell]);
if(e.range.getValue() === true){
sheet.showSheet();
}else{
sheet.hideSheet();
}
}
}
}
I have a spread sheet where users can update a cell's within Column 'J' with notes, however when they update that cell I need my script to run.
This will then copy all the information from Columns 'I' & 'J', and paste them into another cell, then put a Vlookup formula back into 'J' where it has just taken the information from.
It may sound a complicated way about going around things, but because I have various sheets pulling information from different workbooks I need it to happen in this way.
However I have tried numerous solutions and can not get it to run when a cell is changed.
I have tried a installable on change trigger, but this works on the whole workbook and often ends up deleting the information it should be copying.
I have pasted the code I am working in below, any help would be grateful.
function onChange(e)
{
var editRange = { // J:J
top : 2,
bottom : 2000,
left : 10,
right : 10
};
// Exit if we're out of range
var thisRow = e.range.getRow();
if (thisRow < editRange.top || thisRow > editRange.bottom) return;
var thisCol = e.range.getColumn();
if (thisCol < editRange.left || thisCol > editRange.right) return;
//Where Is The Data Stored That You Want To Copy
var ss = SpreadsheetApp.openById("1ZQLKnCHD9acTXCKpTeOnJshHEz43FDPdKZ-U6Me-k_k")
var West = ss.getSheetByName("West Orders")
var WestNotes = ss.getSheetByName("West Notes")
//Ranges of The Data You Want To Copy
var CopyWestNotes = West.getRange(2, 9,West.getLastRow(),2).getValues()
//Clear The Data Before Overiding
var ClearNotes = WestNotes.getRange(2,1,WestNotes.getLastRow(),2).clear()
//Ranges Of The Data You Want To Store
var PasteWestId = WestNotes.getRange(2,1,West.getLastRow(),2).setValues(CopyWestNotes);
//Put a formula in to update Notes;
var formula = '=IFerror(VLOOKUP(i2,\'West Notes\'!A:B,2,false)," ")'
var WestFormula = West.getRange(2,10,West.getLastRow(),1).setValue(formula)
}
The onEdit event
The change trigger is not a simple trigger. You have to install it yourself. There is also no e.range in this event object. I would guess that if you had checked your executions that you would see some errors.
I modified your code to an onEdit() simple trigger. But you may have to make it installable if you're doing anything that requires permissions. And it looks like you are. So now you should be able to continue debugging your code.
function onEdit(e) {
var editRange = {top:2,bottom:2000,left:10,right:10};
var thisRow=e.range.rowStart;
if (thisRow<editRange.top || thisRow>editRange.bottom)return;
var thisCol=e.range.columnStart;
if (thisCol<editRange.left || thisCol>editRange.right)return;
var ss = SpreadsheetApp.openById("id");
var West = ss.getSheetByName("West Orders");
var WestNotes = ss.getSheetByName("West Notes");
var CopyWestNotes = West.getRange(2, 9,West.getLastRow(),2).getValues();
var ClearNotes = WestNotes.getRange(2,1,WestNotes.getLastRow(),2).clear();
var PasteWestId = WestNotes.getRange(2,1,West.getLastRow(),2).setValues(CopyWestNotes);
var formula = '=IFerror(VLOOKUP(i2,\'West Notes\'!A:B,2,false)," ")';
var WestFormula = West.getRange(2,10,West.getLastRow(),1).setValue(formula);
}
I should have mentioned that you'll need to add the id in line 7.
A simple change to line 7 of the script has eradicated the problem, instead of using .openById, I used .getActiveSpreadsheet
So I have a google form feeding into a sheet, and from the spreadsheet I'm making google docs from a template with the answers in the spreadsheet.
The form has the ability to save mid way and come back later via a custom question asking if they want to save and comeback (this submits the form). The script on my spreadsheet activates onFormSubmit so when they quit, the template gets created with half their answers. When they eventually come back and finish it off, I want the script to know where to create the template from.
For instance, 5 more rows were added since they quit and the script creates the template from a manual change of the line 'var tactics' by changing the numbers to the row. e.g. if I was about to test another entry, I'd change the numbers to the next empty row first, then when the form is submitted, it would use that row. Not practical. It wouldn't work.
I've looked around a bit and found onEdit(e) but that doesn't work unless it's a manual entry.
My question is, is there another way other than onEdit to find the last cell UPDATED, not ADDED else it'll grab the last row in the sheet, which I don't want. If it grabs the last cell updated then it will grab the correct row to run the script for. I'll add my script to the bottom if it'll help. ID's etc. have obviously been removed.
Any ideas?
function onFormSubmit(e) {
var Sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
var headers = Sheets.Spreadsheets.Values.get('myID', 'A1:U1');
var tactics = Sheets.Spreadsheets.Values.get('myID', 'A6:U6');
var templateID = "myID"
for(var i = 0; i < tactics.values.length; i++){
var Timestamp = tactics.values[i][0];
var IDCFREF = tactics.values[i][2];
var raisedby = tactics.values[i][4];
var AccMan = tactics.values[i][6];
var Contrib = tactics.values[i][7];
var Contract = tactics.values[i][8];
var CompName = tactics.values[i][9];
var ValidFrom = tactics.values[i][10];
var ValidTo = tactics.values[i][11];
var Freq = tactics.values[i][12];
var PDetailFreq = tactics.values[i][13];
var BillType = tactics.values[i][14];
var TypeOfRebate = tactics.values[i][15];
var RebateDetails = tactics.values[i][16];
var RTarget = tactics.values[i][17];
var GiveDeets = tactics.values[i][19];
var WhyGiveRebate = tactics.values[i][20];
var documentID = DriveApp.getFileById(templateID).makeCopy().getId();
DriveApp.getFileById(documentID).setName('Rebate ' + IDCFREF + ' Request');
var body = DocumentApp.openById(documentID).getBody();
var header = DocumentApp.openById(documentID).getHeader();
header.replaceText('##IDCF##', IDCFREF)
body.replaceText('##REF##', IDCFREF)
body.replaceText('##RAISED##', raisedby)
body.replaceText('##ACCMAN##', AccMan)
body.replaceText('##CONTRIB##', Contrib)
body.replaceText('##SIGNED##', Contract)
body.replaceText('##NAME##', CompName)
body.replaceText('##FROM##', ValidFrom)
body.replaceText('##TO##', ValidTo)
body.replaceText('##FREQ##', Freq)
body.replaceText('##BESPOKE##', PDetailFreq)
body.replaceText('##BILL##', BillType)
body.replaceText('##TYPE##', TypeOfRebate)
body.replaceText('##DEETS##', RebateDetails)
body.replaceText('##TARGET##', RTarget)
body.replaceText('##FULL##', GiveDeets)
body.replaceText('##ELAB##', WhyGiveRebate)
}
}
So for anyone who has the same issue, here was my solution:
function onFormSubmit(e) {
var range = e.range;
var ss = range.getSheet();
var row = range.getRowIndex();
var tactics = Sheets.Spreadsheets.Values.get('ID', "A:AQ"+row);
The way it works is just delete every row in the form responses sheet up until the last entry, and then voila. ("A:AQ"+row) gets the row index from the variable 'row' created from 'e.range' upon form submission.
It works great for me, if 3 forms were filled in and 3 lines were added to the sheet before another came back to edit his responses, the script knows what row to find the data on in the sheet, thus creating the correct template.
I have created a script that allows me use data from a spreadsheet to create a dynamic drop-down list in a Google Form. The data in spreadsheet is a list of dates, but when it gets added to the drop-down list in Google Forms it becomes a string. By the time someone submits a response, the data goes to a spreadsheet and this drop-down's value gets considered as a string instead of a date, e.g. '5/5/2018 instead of 5/5/2018.
var fromRange = 'RequestDates'; //namedRange from Spreadsheet for 'From date' dropdown
var toRange = 'RequestToDates'; //namedRange from Spreadsheet for 'To date' dropdown
function updateDates(e){
// a way to get the items from the form
var form = FormApp.openById("10uyPYIpVtKLg5eaxIrc8di93v7NW1ZSL6gjbtQs4NvE");
var FromList = form.getItemById(1.403587334E9).asListItem();
var ToList = form.getItemById(4.09925665E8).asListItem();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var FormsTest = ss.getSheetByName("AppScript");
// get the values in the first column accept header row 1
var FromValues = FormsTest.getRange('RequestDates').getDisplayValues();
var ToValues = FormsTest.getRange('RequestToDates').getDisplayValues();
var FromNames = [];
for(var i = 0; i < FromValues.length; i++) {
if(FromValues[i][0] != "") {
FromNames[i] = FromValues[i][0];
}
}
var ToNames = [];
for(var y = 0; y < ToValues.length; y++) {
if(ToValues[y][0] != "") {
ToNames[y] = ToValues[y][0];
}
}
// populate the list
FromList.setChoiceValues(FromNames);
ToList.setChoiceValues(ToNames);
}
The list of dates from the spreadsheet gets automatically updated everyday to only allow 12 days after the current day until 180th days after the current day which means the dynamic drop down also gets updated automatically everyday. I'm very new to javascript so any suggestion will be much appreciated.
Try this:
function dateformat(e){ //I presume your in a spreadsheet getting an onFormatSubmit trigger
var sh=e.range.getSheet();
var rg = sh.getRange(2,3,sh.getLastRow(),1);
var data=rg.getValues(); //the plan would be to iterate over all of the rows I guess.
for(var i=0;i<data.length;i++){
var datstr=String(data[i][0]).slice(1);
data[i][0]=new Date(datstr);
}
rg.setValues(data);//This will replace the strings with the date values and you can set the format for that range anyway you wish.
}