I'm trying to figure out an onEdit function for my Google Sheet where, when a checkbox in J16 of the DATA PULL sheet is checked, it will copy the values of the cells in columns F15 to F20 and I17 to E20, then paste them into a new row on the REPORTS sheets on the respective columns and once unchecked it will move to another row, and when checked it will copy the new data.
Really need some help.
Here is the file (https://docs.google.com/spreadsheets/d/19vw1mrwKcvUy66sxXV1r1e2eSlX-O862Ud-Fdi2iEQ8/edit?usp=sharing)
Sorry about that, I had to edit some confidential data and re-organize the design for easier on our end.
Creating function execution table
gs:
function onMyEdit(e) {//use installable so that you can perform actions requiring permissions
//e.source.toast('Entry')
const sh = e.range.getSheet();
if(sh.getName() == "Functions" && e.range.columnStart == 2 && e.value == "TRUE" && e.range.offset(0,-1).getValue() != null) {
//e.source.toast('working')
let n = e.range.offset(0, -1).getDisplayValue();
executeFunctionByName(n);
//Logger.log(n)
e.range.setValue("FAlSE"); //Reset checkbox
}
}
function func1() {
SpreadsheetApp.getActive().toast("func1")
}
function func2() {
SpreadsheetApp.getActive().toast("func2")
}
function executeFunctionByName(func) {
this[func]();
}
Functions Sheet:
Demo:
Description
Your data set is rather complex so I didn't produce a data set to test this but I'm pretty sure it will work for you.
1st. Test that we are on the checkbox in J16 and that the checkbox is true.
2nd. Get the values from I17:I20 a 2D array in the form [[1],[2],[3]...]
3rd. Get the value from E15:E20 a 2D array in the form [[4],[5],[6]...]
4th. Concatinate the 2 arrays [[1],[2],[3]...[4],[5],[6]...]
This would produce a column on a spreadsheet. However we can use the Array.flat() to make it a row [1,2,3...4,5,6...].
Lastly we simply copy the row to the other sheet but we need to convert to a 2D array to use setValues() by enclosing in array brackets [values].
Code.gs
function onEdit(e) {
let src = e.range.getSheet();
let dest = e.source.getSheetByName("Reports");
if( src.getName() === "Data Pull" ) {
if( ( e.range.getA1Notation() === "J16" ) {
if( e.value ) {
let values = src.getRange(17,9,4,1).getValues(); // I17:I20
values = values.concat(src.getRange(15,6,6,1).getValues()); // add E15:E20
dest.getRange(dest.getLastRow()+1,5,1,values.length).setValues([values.flat()]);
}
}
}
}
Reference
Event Objects
Sheet.getRange()
Range.getValues()
Range.setValues()
Array.concat()
Array.flat()
Related
I am extremely new to Google app scripts and I am trying to convert a vba excel spreadsheet over.
Basically, I am first testing functions in a new simple spreadsheet, with the spreadsheet named mycalculator.
I would then like a user to edit to cell C4 which is named distance. and then with that cell change to then update the value in cell C6 which is named conversion with a value of 100.
How do I do this exactly? Needless to say I have tried the code below and without success. Obviously I am trying to compile something from different sources that I have found, but I keep hitting brick walls.
Also, do I need to reference the spreadsheet? Can I not have the script tied to the one sheet only?
With VBA, I was easily able to follow online tutorials, with app script I am struggling to get started.
function onEdit(e) {
var spreadSheet = e.source;
var sheetName = spreadSheet.getActiveSheet().getName();
if(sheetName == 'mycalculator' && 'distance') {
SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('mycalculator').getRange('conversion').setValue(100);
}
}
I'm not sure why you don't just put a formula in C7 =IF(C4="","",100) but here is an onEdit that will do it.
function onEdit(e) {
try {
let sheet = e.range.getSheet()
if( sheet.getName() === "mycalculator" ) {
if( ( e.range.getRow() === 4 ) && ( e.range.getColumn() === 3 ) ) { // cell C4
sheet.getRange("conversion").setValue(100);
}
}
}
catch(err) {
SpreadsheetApp.getActiveSpreadsheet().toast("onEdit() "+err);
}
}
Here as an alternate solution if you really need to use the named range "distance".
function onEdit(e) {
try {
let sheet = e.range.getSheet()
if( sheet.getName() === "Sheet1" ) {
if( e.range.getA1Notation() === sheet.getRange("distance").getA1Notation() ) { // cell C4
sheet.getRange("conversion").setValue(100);
}
}
}
catch(err) {
SpreadsheetApp.getActiveSpreadsheet().toast("onEdit() "+err);
}
}
For your information there are numerous examples at these sites.
SpreadsheetApp
Advance Sheets Service
Best Practices
javascript Tutorial
Good morning:
This time I have a spreadsheet in Google Sheets, in which in column "A", users will be entering their identity document. Ideally, numerical or alphanumeric values should be entered in the case of foreign documents. But, many times these users paste the data from other forms, so they may come with special characters ("." ;","; "-"; "_"). The idea is that when the user enters a data, a macro is run, which removes the format of that entire column, cleans it and eliminates those special characters, only leaving numbers and letters to have.
The easy step is to do it with a formula in a contiguous column, the formula is REGEXREPLACE(), but already the document becomes heavier and such. That's why I wanted to use the macro option, since it would be acting on the same value of the cell and reducing it in many cases.
I still have no knowledge of Appscript and have been searching on Youtube, but I did not find anything.
Could you help me create this macro?
I thank you very much for being so!!!
Miguel.-
Remove unwanted whitespace:
function onEdit(e) {
//e.source.toast("Entry");
const sh = e.range.getSheet();
const names = ["Sheet1","Sheet2"];//You can changes these sheet names and add as many as you wish
const idx = names.indexOf(sh.getName());
if(~idx && e.range.columnStart == 1 && e.value) {
//e.source.toast('Gate1')
e.range.setValue(e.value.replace(/[-_,:;\.]+/g,''));
}
}
Regex
Learn More
Bitwise Not(~)
try this:
function onEdit(e) {
//e.source.toast("Entry");
const sh = e.range.getSheet();
const names = ["Sheet1","Sheet2"];//You can changes these sheet names and add as many as you wish
const idx = names.indexOf(sh.getName());
if(~idx && e.range.columnStart == 1 && e.value) {
//e.source.toast('Gate1')
sh.getRange("A5:A" + sh.getLastRow()).getValues().flat().forEach((el,i) => {
sh.getRange(i + 5,1).setValue(el.replace(/[-_,:;\.]+/g,''));
})
}
}
Here is an example of an onEdit(e) function that will remove unwanted characters. The String.replace() is using a regex expression with the special character dot escaped \.. The same would need to be done for any other special characters.
The onEdit is limited to column A of Sheet1.
function onEdit(e) {
try {
if( e.range.getSheet().getName() !== "Sheet1" ) return;
if( e.range.getColumn() !== 1 ) return;
let value = e.value;
if( !value ) { // copy/paste
value = e.range.getValue();
}
value = value.replace(/[-_,;\.]/g,'');
e.range.setValue(value);
}
catch(err) {
SpreadsheetApp.getActiveSpreadsheet().toast("onEdit() "+err);
}
}
Reference
onEdit(e) trigger
String.replace()
Regexr
Here is my test sheet.
Goal: whenever I click on cells A5:A10 in 'Sheet 1', I want the value of A1 to change to B5:B10.
For example: if I click A7, A1 = B7.
Note: I don't want this script to run for any other sheet or document.
Can you please help me create a script to run automatically for this purpose?
Explanation:
Indeed, the onSelectionChange(e) trigger is what you are looking for.
You just need to take advantage of the event object to capture information of the selected cell.
When you click on a particular cell in range A5:A10 of Sheet1 the following script will update the value of cell A1 to the corresponding value of B5:B10.
What is important here is to understand the if condition I used:
if (as.getName() == 'Sheet1' && row>4 && row<11 && col==1)
Essentially, I am asking for selections only in Sheet1, after row 4 and before row 11 and column 1. That is basically the range A5:A10.
Solution:
function onSelectionChange(e) {
const as = e.source.getActiveSheet();
const row = e.range.getRow();
const col = e.range.getColumn();
if (as.getName() == 'Sheet1' && row>4 && row<11 && col==1){
as.getRange('A1').setValue(as.getRange(row,2).getValue());
}
}
You could also use offset to get the value of the next column instead of hardcopying the number 2.
Replace:
as.getRange('A1').setValue(as.getRange(row,2).getValue());
with:
as.getRange('A1').setValue(e.range.offset(0,1).getValue());
but both approaches work just as fine.
As an alternative to what Marios suggests, I prefer exiting as early as possible (since the onSelectionChange can fire very rapidly, I find it somewhat more performant). So, you can move your check to the top of the function (the rest still apply):
function onSelectionChange({ range }) {
const sh = range.getSheet();
const shname = sh.getSheetName();
if( shname !== "<sheet name here>" ) { return; }
//continue if ok
}
Note that usually, it is better to put the sheet name in a configuration object (or, even better, in a function that returns a configuration object) for easy maintenance.
Also, since each sheet has a unique Id (you can visually find it in the gid anchor of the open spreadsheet URL or programmatically with the method mentioned below), you could save you some trouble if the sheet gets renamed and check for id match instead with getSheetId:
function onSelectionChange({ range }) {
const sh = range.getSheet();
const id = sh.getSheetId();
if( id !== 123456789 ) { return; }
//continue if ok
}
I have a column of check boxes:
.
If a box is checked it sets a value to a cell in another sheet.
If I check box no.1 ,it turns true and the remaining still false
then if I check box no.2 it also turns true long with box no.1 and the remaining still false. This is the normal operation but I need that, when I check a box it turns true and all the other boxes turn false, either they are checked or not.In other words, I want one box to be checked at a time.
Can I do that?
This is my code to set a value if the box is checked:
var hasValue = sheet.getRange("B2:B").getValues();
for (var i = 0; i < hasValue.length; i++) {
if (hasValue[i][0] == true) {
var transfer = sheet2.getRange(2, 2, 1, 1).setValue(i + 1);
}
}
This kind of behavior is known as a "radio button".
The simplest method to achieve it is to bind the simple edit trigger:
inspect the edited range to determine if it was to your checkbox region and quit if not.
set all checkboxes to false
set the edited cell to the appropriate value from the event object
if required, perform the update
An extremely minimal sample which you will have to configure, and which is only configured for single-cell edits.
function onEdit(e) {
if (!e || e.value === undefined)
return; // The function was run from the Script Editor, or a multi-cell range was edited.
const edited = e.range;
const s = edited.getSheet();
if (s.getName() !== "some name")
return; // A cell on the wrong sheet was edited
if (isCheckboxRow_(edited.getRow()) && isCheckboxCol_(edited.getColumn())) {
// The cell edited was in a row and a column that contains a checkbox
updateCheckboxes_(s, edited, e);
}
}
function isCheckboxRow_(row) {
// Assumes checkboxes are only in rows 5, 6, 7, 8, 9, and 10
return row >= 5 && row <= 10;
}
function isCheckboxCol_(col) {
// Assumes checkboxes are in column A
return col === 1;
}
function updateCheckboxes_(sheet, editRange, eventObject) {
if (!sheet || !edit || !eventObject)
return; // Make sure all required arguments are defined (i.e. this was called and not run from the Script Editor)
const cbRange = sheet.getRange("A5:A10"); // location of the checkboxes in a radio group.
cbRange.setValue(false);
editRange.setValue(eventObject.value);
// Reference some other sheet
const targetSheet = eventObject.source.getSheetByName("some other sheet name")
if (!targetSheet)
return; // the sheet name didn't exist in the workbook we edited.
// Reference a cell in the same row as the cell we edited, in column 1
const targetCell = targetSheet.getRange(editRange.getRow(), 1);
if (eventObject.value) {
// when true, give the target cell the value of the cell next to the edited checkbox
targetCell.setValue(editRange.offset(0, 1).getValue());
// do other stuff that should be done when a checkbox is made true
} else {
// the checkbox was toggled to false, so clear the target cell
targetCell.clear();
// do other stuff that should be done when a checkbox is made false
}
}
The above hints at some suggested practices, such as using helper functions to encapsulate and abstract logic, resulting in easier to understand functions.
Review:
Simple Triggers
Event Objects
Spreadsheet Service
As I mentioned I would us an onEdit(event) to monitor which checkbox has been checked and loop through the column and only set one checkbox to true. Note that in your code snippet, getRange("B2:B") could be 999 rows. I use getDataRange() to limit to only the rows that are used. And I use getCriteriaType() to check that it is a checkbox not some other data type. And I'm assuming on your sheet2 you want to record which box was last checked true. tehhowch's answer is more generic and maybe more than what you need so here is a limited specific answer.
function onEdit(event) {
try {
var sheet = event.range.getSheet();
// Limit the following code to a particular sheet
if( sheet.getName() === "Sheet5" ) {
// Limit the following code to column B
if( event.range.getColumn() === 2 ) {
var range = sheet.getRange(2,2,sheet.getLastRow()-1,1);
var checks = range.getValues();
var valid = range.getDataValidations();
for( var i=0; i<checks.length; i++ ) {
if( valid[i][0].getCriteriaType() === SpreadsheetApp.DataValidationCriteria.CHECKBOX ) checks[i][0] = false;
}
// Assuming there are no formulas in this range
range.setValues(checks);
event.range.setValue(event.value);
if( event.value === true ) {
event.source.getSheetByName("Sheet6").getRange(2,2,1,1).setValue(event.range.getRow());
}
}
}
}
catch(err) {
SpreadsheetApp.getUi().alert(err);
}
}
I am trying to create a script on Google Sheets to receive a message alert whenever the value of the B10 cell increases. Something like this:
Static oldVal As Variant
oldVal = Me.Range("B10").Value
If Me.Range("B10").Value > oldVal Then
Browser.msgBox('hello world');
End If
There is a way?
Other than your syntax being way off (Apps Script is based on JavaScript), yes, you can do that using a couple different mechanisms.
Use the onEdit(e) simple trigger to watch the sheet.
The e object has an oldValue and a value key you can compare directly. You can also test for a specific cell reference using the range parameter. The following tests for cell B10, specifically:
function onEdit(e) {
if (e.range.getA1Notation() == 'B10' && e.value > e.oldValue) {
Browser.msgBox("you increased the value")
}
}
Taking it even farther, you can specify a sheet within a Spreadsheet to test before the script will run.
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
if (e.range.getA1Notation() == 'B10' && e.range.getSheet().getName() == 'Página1' && e.value > e.oldValue) {
Browser.msgBox("you increased the value")
}
}
If the cell is not in the specified sheet or range,, nothing happens.
Watching a formula cell is a little more complex, but it will work. There are two methods in Apps Script. getValue() returns the value displayed in the cell, even if it's calculated by a formula. .getFormula() gets the cell's calculating formula. So, yes, this will work with simply calling .getValue() on the range.
The major difference is that you have to watch the formula cell because it is not directly edited by the user. In other words, the event object will not trigger if the cell value is calculated by a formula. Including it is easy:
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var formulaRange = e.range.getSheet().getRange("B4");
if (e.range.getA1Notation() == 'B10' || formulaRange && (e.range.getSheet().getName() == 'Página1' && e.value > e.oldValue) {
Browser.msgBox("you increased the value")
}
}
More on Apps Script event objects in the documentation.