Trigger that fires when a new cell selection is made - google-apps-script

Have successfully implemented OnEdit/OnChange triggers. However, these require an actual change to a cell.
It does not look like there is an analogous OnSelectionChanged trigger that fires when a new cell selection is made.
Any thoughts on how one might implement?

Good news. There is a new Simple Trigger, check this release note:
A new simple trigger, onSelectionChange(e), has been added for
Google Sheets. The onSelectionChange(e) trigger runs automatically
when a user changes the selection in a spreadsheet.
The following code illustrates how to use this trigger:
function onSelectionChange(e) {
var range = e.range;
if(range.getNumRows() === 1 && range.getNumColumns() === 1) {
SpreadsheetApp.getActiveSheet().clearFormats();
range.setFontColor("red").setFontWeight("bold")
}
}
If you add this function in your sheet bounded script the selected cell will always be highlighted as bold red. Please, note that this is just an example. The clearFormats() call will clear the format of all cells in the active sheet, so take care with it.

Related

Need to disable the dropdown once the option is selected

Once the option in the dropdown is selected, that cell needs to be disable and no further changes it should permit.
Basically need to remove the edit rights for that particular cell once the option is selected or anything is entered in that dropdown cell(while dragging and filling the dropdown option).
SS Link: https://docs.google.com/spreadsheets/d/127P05KL1tzGru8PMXFLM57y20m1qOVnRS8IAfptsIh4/edit?usp=sharing
Since I am new to google script, I am sure it is possible but don't know the way out.
I believe your goal is as follows.
You want to protect the cell when the dropdown list of column "A" is changed.
In this case, how about the following sample script?
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet and save the script. And, in order to run the script when the dropdown list of column "A" is changed, please install OnEdit trigger to the function installedOnEdit.
When you want to use this script, please change the dropdown list of the column "A". By this, the script is automatically run by the installable OnEdit trigger.
function installedOnEdit(e) {
const { range } = e;
const sheet = range.getSheet();
const dataValidation = range.getDataValidation();
if (sheet.getSheetName() != "Sheet1" || range.columnStart != 1 || !dataValidation || dataValidation.getCriteriaType() != SpreadsheetApp.DataValidationCriteria.VALUE_IN_LIST) return;
var protect = range.protect();
protect.addEditor(Session.getEffectiveUser());
protect.removeEditors(protect.getEditors());
if (protect.canDomainEdit()) {
protect.setDomainEdit(false);
}
}
When this script is run, the cell is protected. In this case, only the owner can edit the cell.
Note:
This script was tested using your provided Spreadsheet. When you change Spreadsheet, this script might not be able to be used. Please be careful about this.
References:
Installable Triggers
protect()

Trigger bug when copying rows

I have a function that copies rows from one sheet to another, which works when I run it manually:
function updateDataRange() {
var formSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form Responses 1");
var lastFormRow = formSheet.getLastRow();
var dataSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("DataRange");
dataSheet.clear();
for(var rowCounter = 1; rowCounter <= lastFormRow; rowCounter++) {
var sourceRange = formSheet.getRange('B'+rowCounter+':H'+rowCounter);
var insertRow = dataSheet.getLastRow() + 1;
var targetRange = dataSheet.getRange('A'+insertRow);
sourceRange.copyTo(targetRange);
}
}
However, when I run this via a trigger (e.g. function onEdit(e) { updateDateRange(); } ), there are gaps in the rows and some of the values will be left out. In this case, I know there is a "fix" by using rowCounter instead of insertRow to write the files instead of using getLastRow(), but one can easily see how this is a problem in another scenario.
So I guess my question is simple: Why isn't this working correctly when using a trigger?
Edit: To clarify, I have a 3rd sheet with some conditions/cells (i.e. conditions that influence which rows are to be copied) and changing them (like changing a different date) trigger the function.
Explanation:
You are using an onEdit trigger to capture sheet changes. But onEdit triggers work only when the user changes the value of a cell. Referencing the official documentation:
The onEdit(e) trigger runs automatically when a user changes the value
of any cell in a spreadsheet.
In your case, I think you are trying to execute this code when the Form Responses sheet is filled in with data by the form.
There are two kind of trigger functions that work with form submissions and they are both installable.
One is a google sheet event based trigger and the other one a form event based trigger.
Your goal is to execute some code from the google sheets side, so it makes sense to use the first one.
Modifications:
Change the name of the function from onEdit to a different name of your choice e.g. myFormSubmitTrigger.
Since the trigger is installable you need to "install" a onFormSubmit trigger for the myFormSubmitTrigger function. Here you can find some simple instructions on how to do that.
If your question is why doesn't your function work when the form makes changes to sheet Form Responses 1 then Marios has answered you question accept it and move on. If your trying to run your function when a user is making edits to sheet Form Responses 1 using a simple trigger then a possible explanation for not getting all of the rows completely copied is that simple trigger must complete in 30 seconds. If you continue to accept more data in Form 1 Responses then soon or later you will have problems but with this function it will be a lot later because it will run much faster than your code:
function updateDataRange() {
const sss=SpreadsheetApp.openById('ssid');
const ssh=e.source.getSheetByName('Form Responses 1');
const sr=2;
const svs=ssh.getRange(sr,2,sh.getLastRow()-ssr+1,7).getValues();
const dsh=e.source.getSheetByName('DataRange');
dsh.clear();
dsh.getRange(1,1,svs.length,svs[0].length).setValues(svs);
}
However, I would recommend that you never edit a form responses sheet. I would consider using the onFormSubmit trigger to capture all of the data to a second sheet that is available to edit. And it will have additional data automatically appended to it via dsh.appendRow(e.values). And so now you would no longer require an onEdit trigger because your data sheet is kept upto data with an onFormSubmit trigger and you may feel free to edit the datasheet any time you wish.
If neither of these questions suffices then I would recommend that you be more clear about what you are asking.

Google Sheets Copy down formula with inserting a row

I am transitioning from Excel and VBA to Google Sheets and admittedly, don't know JavaScript.
I see that there is a common request for some VBA which will copy down the formulas from the row above, when I insert a new row anywhere in the dataset. I know how to add a butt, and i used the Macro Recorder to record the insertion of a Row, but the recorder only records that particular row that I chose to insert above.
I'd like the script to grab the activecell row number, so that it is dynamic on where ever my cursor is.
Here's the code I have so far. Can someone help me correct it?
function test() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getActiveCell().getRow().activate();
spreadsheet.getActiveSheet().insertRowsBefore(spreadsheet.getActiveCell().getRow(), 1);
spreadsheet.getActiveCell().offset(0, 0, 1, spreadsheet.getActiveCell().getNumColumns()).activate();
};
Solution:
You can accomplish your goal by creating an installable onChange trigger.
Follow the instructions below to set it up. After the setup is completed, you don't execute myFunction manually. It will be executed automatically everytime you insert a new row in the active sheet.
function myFunction(e) {
const sh = SpreadsheetApp.getActiveSheet();
if(e.changeType === 'INSERT_ROW') {
const row = sh.getActiveRange().getRow();
const formulas = sh.getRange(row-1,1,1,sh.getLastColumn()).getFormulasR1C1();
sh.getRange(row,1,1,sh.getLastColumn()).setFormulasR1C1(formulas);
}
}
Instructions:
Go to use the aforementioned code, copy/paste it to the script editor and click save.
Click on the current project's triggers:
Click on add trigger on the bottom right side of the page.
And then use exactly these settings:

How to protect conditional formatting in google sheet?

I have 2 CFs in a sheet that I would like to keep upon copy/pastes you can see the range and formulas below.
CF1 Range : B3:H1001
CF1 Formula : =($A3<>"")*(B3="") //This one higlights the empty cells in a given range
CF2 Range : A3:R1001 // This one highlights the row if the cell in R column is No.
CF2 Formula : =$R:$R="No"
Is there a way to protect these CFs or apply them again after copy paste ? Thanks !
When I copy paste from another range I am losing the Conditional formats that i have created. So I want them to stay like I formatted or I would like them to be applied again after the copy/paste.
From above replying, I could understand that you want to keep the conditional formats, which are =($A3<>"")*(B3="") for B3:H1001 and =$R:$R="No" for A3:R1001, even when the range is copied and pasted to B3:H1001 and A3:R1001.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Issue and workaround:
In the current stage, unfortunately, there are no methods for directly protecting the conditional formats in Google Apps Script. So in this case, as a workaround, I would like to propose to overwrite the conditional formats when the change of conditional formats and cells using OnChange event trigger.
Flow:
The range is copied and the parameters of conditional formats are changed.
The script is automatically run by the OnChange event trigger.
The existing conditional formats are deleted and new conditional formats are set.
Usage:
1. Copy and paste sample script
Please copy and paste the following sample script.
Sample script:
Please set the sheet name.
function onChange(e) {
const sheetName = "Sheet1"; // Please set the sheet name.
if (e.source.getActiveSheet().getSheetName() != sheetName || e.changeType != "OTHER") return;
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
var rules = sheet.clearConditionalFormatRules();
const rule1 = SpreadsheetApp.newConditionalFormatRule()
.setRanges([sheet.getRange("B3:H")])
.setBackground("green")
.whenFormulaSatisfied('=($A3<>"")*(B3="")').build();
const rule2 = SpreadsheetApp.newConditionalFormatRule()
.setRanges([sheet.getRange("A3:R")])
.setBackground("green")
.whenFormulaSatisfied('=$R:$R="No"').build();
sheet.setConditionalFormatRules([rule1, rule2]);
SpreadsheetApp.flush(); // This line might not be required.
}
In this sample script, 2 conditional formats in your question are set as the background color of green.
2. Install OnChange event trigger
Please install the OnChange event trigger to the function of onChange.
3. Test run
As a test run, please copy and paste the range in sheetName. By this, the script is run by the OnChange event trigger.
Note:
I think that OnEdit event trigger can be also used. But in this case, when the parameters of the conditional formats are changed, the event trigger is not run. So I used the OnChange event trigger.
References:
Installable Triggers
setConditionalFormatRules(rules)

In AppScript, when multiple ranges are edited at once, How is rangeList obtained?

I wrote script for Google Sheets which is triggered by an onEdit event.
To ilustrate my problem, let's suppose that the contents of ranges A3:A5, C3:C5 and E1:E8 were selected and then deleted at once by pressing [delete]. In this situation, the onEdit(e){...} function would be called. The problem is that object e only has information about the first edited range: e.range.getA1Notation() == 'A3:A5'.
I tried obtaining the other edited ranges by calling SpreadsheetApp.getActiveSheet().getActiveRangeList().getRanges(); but only the first range is being returned.
The way to do it would be:
function onEdit(e) {
var selection = e.source.getSelection();
var ranges=selection.getActiveRangeList().getRanges();
for (var i = 0; i < ranges.length; i++) {
Logger.log(ranges[i].getA1Notation());
}
}
However, only the first range will be returned if you run the function onEdit() instead of manually with var selection = SpreadsheetApp.getActiveSheet().getSelection();.
This seems to be a bug and has already been filed on Issuetracker:
https://issuetracker.google.com/issues/115931946
I suggest you to give it a "star" to increase visibility.
You want to retrieve all selected ranges when the OnEdit event trigger is fired.
In your case, when the cells of A3:A5, C3:C5 and E1:E8 are manually deleted, you want to retrieve the ranges of A3:A5, C3:C5 and E1:E8.
If my understanding is correct, how about this answer? Please think of this as just one of several answers.
Issue:
At first, I summarized the current situation as follows. As the sample case, it supposes the situation that the cells of A3:A5, C3:C5 and E1:E8 are deleted.
The event object of OnEdit event trigger cannot return the values of multiple ranges like A3:A5, C3:C5 and E1:E8.
When the OnEdit event trigger is fired, the script of by running the trigger returns only A3:A5 which is the first selected range.
The script is SpreadsheetApp.getActiveSpreadsheet().getSelection().getActiveRangeList().getRanges().
When the above script is run by the script editor, A3:A5, C3:C5 and E1:E8 are returned.
These were mentioned by XY6 and ziganotschka. I also think that this might be a bug.
From above situation, I guessed that when the OnEdit event trigger is fired, if the script is indirectly run by the trigger, SpreadsheetApp.getActiveSpreadsheet().getSelection().getActiveRangeList().getRanges() might return A3:A5, C3:C5 and E1:E8. By this, I thought the following workaround.
Workaround:
In this workaround, above my guess was achieved using a dialog. The flow of this workaround is as follows.
Cells of A3:A5, C3:C5 and E1:E8 on the Spreadsheet are manually deleted.
The installable OnEdit event trigger is fired and a script is run.
The script opens a dialog.
The side bar can be also used for this situation.
The script of SpreadsheetApp.getActiveSpreadsheet().getSelection().getActiveRangeList().getRanges() is run using Javascript at the dialog.
By this, running indirectly getSelection can be achieved. As the result, it was found that this workaround can retrieve all selected ranges when the OnEdit event trigger is fired.
Sample script:
The sample script for this workaround is as follows. When you use this script, please install myFunction() as the installable OnEdit event trigger.
function myFunction() {
var script = "<script>google.script.run.withSuccessHandler((e)=>{google.script.host.close()}).getSelectedRanges()</script>";
var html = HtmlService.createHtmlOutput(script).setWidth(100).setHeight(100);
SpreadsheetApp.getUi().showModalDialog(html, "sample");
}
function getSelectedRanges() {
var selection = SpreadsheetApp.getActiveSpreadsheet().getSelection().getActiveRangeList().getRanges();
var ranges = selection.map(function(e) {return e.getA1Notation()});
Logger.log(ranges) // ["A3:A5", "C3:C5", "E1:E8"]
}
When the cells of A3:A5, C3:C5 and E1:E8 on the active Spreadsheet are manually deleted, a dialog is opened by myFunction(), and Javascript of the dialog runs getSelectedRanges(). Then, the dialog is automatically closed.
References:
Installable Triggers
Class Ui
Class HtmlService
Class Selection
If I misunderstood your question and this was not the direction you want, I apologize.