Replace values with Google Sheets AppScript - google-apps-script

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

Related

Checkbox in Sheets as Enter button to Copy Data

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()

How to uppercase a cell range even if user type or paste lowercase with no warning in Google Sheet

I've made a simple table in a one of many sheets in a google sheet file and I would like a cell range of a sheet to alway appear upper case no mater what user input without any warning.
Currently I found and used the script below in Apps Script and it works on all sheets and only on input texts, not pasted texts, but I would like to upper case a cell range on a sheet only.
function onEdit(e) {
if (Object.prototype.toString.call(e.range.getValue()) !== "[object Date]" ) {
if (!e.range.getFormula()) {
e.range.setValue(e.value.toUpperCase());
}
}
}
Can someone help please? Thanks
Something like this
function lfunko() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("SheetName");
const rgA1 ="Whatever"
let vo = sh.getRange(rgA1).getDisplayValues().map(r => {
let row = []
r.forEach(e => row.push(e.toUpperCase()));
return row
})
sh.getRange(rgA1).setValues(vo);
}
Try:
function onEdit(e) {
if (e.source.getActiveSheet().getName() === `Trade History`) {
if ((e.range.columnStart >= 2 && e.range.columnEnd <= 3) && (e.range.rowStart >= 2 && e.range.rowEnd <= 1000)) {
const values = e.range.getDisplayValues().map(i => i.map(item => String(item).toUpperCase()))
e.range.setValues(values)
}
}
}
This works by first checking the event happened on the correct sheet by name, then checks the changed cells occurred within a specified range (from your comment, the current range is B2:C1000).
If the changed cells meet these conditions, the values from the range are then converted to UpperCase and set.

Automatic first letter to upper case?

I'm trying replace first letter of the text in a cell to upper case when a cell edited. For that I'm using onEdit script trigger:
function onEdit(e)
{
e.range.setValue(e.range.getValue().replace(/^./, a => a.toUpperCase()));
}
It works well, until I try undo in the spreadsheet, than onEdit fires again and replaces the text again.
So is there a better way to achieve this?
First Letter to UpperCase
function onEdit(e) {
//e.source.toast('Entry')
const sh = e.range.getSheet();
if(sh.getName() == 'Sheet0' && e.range.columnStart == 1 && e.range.rowStart > 1 && e.value) {
//e.source.toast('Flag1')
e.range.setValue(e.value.replace(/^\s*(\w)/, a => a.toUpperCase()));
}
}
Apparently the event object that onEdit receives contains value and oldValue properties, they both set to undefined when used undo.
So this seems to be working much better (it also ignores white space infront):
function onEdit(e)
{
if (e.value)
{
const val = e.value.replace(/^\s*./, a => a.toUpperCase());
if (val !== e.value)
e.range.setValue(val);
}
}
It still not a perfect solution, because it has delay in the execution and also it might skip cells if multiple edits in multiple cells were done in short period of time.

Creating multiple function onEdit(e) to work in one script

My script below works as an independent script - It will add a date and a time in a cell/column opposite the trigger cell/ column. I.e. Any cell in Column A stating TRUE will have in its adjacent Column B cell stating the exact date & time, at any moment the cell in Column A is deemed to be TRUE. The script keeps this as as one single change. If I were to delete TRUE or change it to any other text, the data in column B would remain the same, until I write TRUE again - This will force the cell(s) in Column B to overwrite the previous date/time with the latest date/time the moment the new "TRUE" statement was issued in Column A.
Now, I would like to add another onEdit function where it works on Sheet 2, but the column reference numbers are not 1 (as you will see below, all numbers are 1, whether positive or negative), but will be 2. I do not know how to get the two separate functions to work as when I list them separately, with an identical script structure, only the last script in the sequence works, and the first one does not.
I hope I have made this clear enough; I am not a coder by any means, and I have done my best with tutorials, forums, etc before posting this question. I can learn better by reverse-engineering someone else's work rather than being given the building blocks and told to create something, but I appreciate any education on this so I can learn for the future.
function onEdit(e) {
var aCell = e.source.getActiveCell(), col = aCell.getColumn();
if(col == 1) { //number of column(s) where you have a data
var adjacentCell = aCell.offset(-1,-1);
var newDate = Utilities.formatDate(new Date(),
"GMT+1", "dd/MM/yyyy hh:mm:ss");
adjacentCell.setValue(newDate);
}}
function onEdit(e){
var ss = SpreadsheetApp.getActiveSheet();
//change to be your sheet name so the script only triggers on that sheet
if( ss.getName() == "Sheet1" ) { //checks that we're on the correct sheet
var r = ss.getActiveCell();
if(e.value != "TRUE") return;
e.source.getActiveSheet().getRange(e.range.rowStart,e.range.columnStart+1).setValue(new
Date());
}
}
I don't think that you have specified sufficient conditions for this to work correctly but this is what you have so far:
function onEdit(e) {
const sh = e.range.getSheet();
if(sh.getName() == 'Sheet1' && e.range.columnStart == 1) {
e.range.offset(-1,-1).setValue(Utilities.formatDate(new Date(),"GMT+1", "dd/MM/yyyy hh:mm:ss"));
}
if(sh.getName() == 'Sheet1' && e.value == "TRUE") {
e.range.offset(0,1).setValue(new Date())
}
}
I imagine you will need further debugging to get this to work.

onSelectionChange for a specific Google Sheets tab

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
}