Google Sheets, cell counter that counts every time a cells values change - google-apps-script

I'm trying to get a counter going in google sheets that will count how many times a cell has changed or updated.
I want cell "K1" to count how many times cell "Client!A2" values have changed.
This is my first time using Apps-Script so I feel like I must be missing something here. This is what has been input into the code.gs section.
function onEdit(e) {
if(e.range.getA2Notation() == "Client!A2") {
var sCounter = e.source.getRange("K1");
var counter = sCounter.getValue();
if(counter === 0) {
counter = 1;
} else {
counter ++;
}
sCounter.setValue(counter);
}
}
It doesn't seem to be working,
Am I supposed to be putting anything else in the brackets? Or am I just doing it wrong altogether?

function onEdit(e) {
const sh = e.range.getSheet();
const shts = ['Sheet1','Sheet2'];//included sheets
const idx = shts.indexOf(sh.getName());
if(~idx && e.value != e.oldValue) {
let n = Number( PropertiesService.getScriptProperties().getProperty('editcounter'));//counter
PropertiesService.getScriptProperties().setProperty('editcounter',++n);
sh.getRange('K1').setValue(n);
e.source.toast(n);
}
}

Related

How to put the sheet name in a cell depending on another cell changes?

I have a Google sheets to change the App store content with each version of our app, for example: changing the App description, changing the App icon, changing the keywords, etc. However, we don't change all of these items with every update. My goal is to have the "C" column track which of these in column "B" was last changed in what version and put the sheet name in the "C" column from that sheet onwards so that it updates automatically instead of me doing so manually.
So basically, it would be something like: If B2 changed, put the sheet name in C2 from this sheet onwards without affecting the previous sheets. and if C2 = the current sheet name, highlight C2.
I'm sorry if I couldn't explain it well, but hopefully, my sheet will help get the situation better
Sheet Link
I managed to get the sheet name from an app script, but I couldn't link it with the "B" column, but it get the name only once and doesn't update it if the sheet name is changed or when I duplicate it.
Here's the function:
function mySheetName() {
var key = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getName();
return key;
}
I tried to use the =Cell formula, but that didn't have what's required.
Tried searching for a get-around but didn't find anything similar to my current situation.
I created a simple example that should solve your problem.
Just add this auxiliary functions on your app script, and set the values on "C" column to:
=IF(GetPreviousSheetValue(ROW(), COLUMN()-1, dummy!$A$1) <> INDIRECT(ADDRESS(ROW(), COLUMN()-1)), GetSheetName(), GetPreviousSheetValue(ROW(), COLUMN(), dummy!$A$1))
The app script functios are:
function onEdit(e) {
SpreadsheetApp.getActive().getSheetByName('dummy').getRange('A1').setValue(Math.random());
}
function GetSheetName() {
return SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getName();
}
function GetAllSheets() {
return SpreadsheetApp.getActiveSpreadsheet().getSheets();
}
function GetAllSheetNames() {
var out = new Array();
var sheets = GetAllSheets();
for (var i = 0 ; i < sheets.length; i++) {
out.push( [ sheets[i].getName() ] );
}
return out;
}
function GetPreviousSheetName(dummy) {
let sheetName = GetSheetName();
let allSheetNames = GetAllSheetNames();
for (var i = 0 ; i < allSheetNames.length; i++) {
if (sheetName == allSheetNames[i] && i + 1 < allSheetNames.length) {
return allSheetNames[i + 1];
}
}
return "";
}
function GetPreviousSheetValue(row, col, dummy) {
let sheetName = GetSheetName();
let allSheets = GetAllSheets();
for (var i = 0 ; i < allSheets.length; i++) {
if (sheetName == allSheets[i].getName() && i + 1 < allSheets.length) {
return allSheets[i + 1].getRange(row, col).getValue();
}
}
return "";
}
PS: Since google cache the functions return values, we have to add a dummy parameter to GetPreviousSheetName and GetPreviousSheetValue so these functions re-run and update the cells correctly whenever the sheets changes. For more info take a look at link
And this is the end spreadsheet:
Image1
Image2

Transferring Rows between Sheets via a like Identifier

Evening everyone!
I asked this about a week back, but I think the thread got lost in the ether. We came close, but I'm trying to create a function where "Transfer a range of Rows from sheet 1 to sheet 2. Sheet 1 has order IDs in column E. G will have =unique to show me all current order IDs, with check boxes next to each unique reference. Check the box next to which ones you want to CUT over > Select a menu run add on > Run Script > all Rows from A:E that match the desired ID are moved".
[Picture Reference]
Sheet Reference
function onEdit(e) {
e.source.toast('Entry')
const sh = e.range.getSheet();
if(sh.getName() == "Reference" && e.range.columnStart == 8 && e.range.rowStart > 1 && e.value == "TRUE") {
e.source.toast('Gate1')
let rg = sh.getRange(e.range.rowStart,1,1,5)
let vs = rg.getValues();
const osh = e.source.getSheetByName("Processing");
osh.getRange(osh.getLastRow() + 1,1,1,5).setValues(vs);
rg.deleteCells(SpreadsheetApp.Dimension.ROWS);
e.range.setValue("FALSE");
}
}
Here is what we had so far. Please let me know if anyone can help, thank you!
To get all rows that match the unique ID whose checkbox was ticked, use Array.filter(), like this:
/**
* Simple trigger that runs each time the user hand edits the spreadsheet.
*
* #param {Object} e The onEdit() event object.
*/
function onEdit(e) {
if (!e) {
throw new Error(
'Please do not run the onEdit(e) function in the script editor window. '
+ 'It runs automatically when you hand edit the spreadsheet.'
+ 'See https://stackoverflow.com/a/63851123/13045193.'
);
}
moveRowsByUniqueId_(e);
}
/**
* Triggers on a checkbox click and moves rows that match a unique ID.
*
* #param {Object} e The onEdit() event object.
*/
function moveRowsByUniqueId_(e) {
let sheet;
if (e.value !== 'TRUE'
|| e.range.rowStart <= 1
|| e.range.columnStart !== 8
|| (sheet = e.range.getSheet()).getName() !== 'Reference') {
return;
}
e.source.toast('Moving rows...');
const uniqueId = e.range.offset(0, -1).getValue();
const range = sheet.getRange('A2:E');
const values = range.getValues();
const targetSheet = e.source.getSheetByName('Processing');
const _matchWithId = (row) => row[4] === uniqueId;
const valuesToAppend = values.filter(_matchWithId);
if (uniqueId && valuesToAppend.length) {
appendRows_(targetSheet, valuesToAppend);
range.clearContent();
const remainingValues = values.filter((row) => !_matchWithId(row));
range.offset(0, 0, remainingValues.length, remainingValues[0].length)
.setValues(remainingValues);
e.source.toast(`Done. Moved ${valuesToAppend.length} rows.`);
} else {
e.source.toast('Done. Found no rows to move.');
}
e.range.setValue(false);
}
For that to work, you will need to paste the appendRows_() and getLastRow_() utility functions in your script project.
It work almost like asked but :
it's using a personal lib (available below)
didn't make the part realtiv of removing range and aggregate result, I hope i can add it to the lib some day. However, empty cell are fill with -
for an obscure reason, it doesn't like the TRUE/FALSE cell, but work like a charm with 1/0 or any other texte value, regex, ...
Additional error handling are to be added if not any match or others possibilites
function onEdit(e){
console.log(SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Reference").getRange("H3").getValue())
var tableReference = new TableWithHeaderHelper(SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Reference").getRange("A1").getDataRegion());
var tableReferenceId = new TableWithHeaderHelper(SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Reference").getRange("G11").getDataRegion());
var tableProcessing = new TableWithHeaderHelper(SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Processing").getRange("A1").getDataRegion());
// get value
var id = tableReferenceId.getTableWhereColumn("Move to Sheet").matchValue(1).getWithinColumn("Unique Filter").cellAtRow(0).getValue();
var tableWithinId = tableReference.getTableWhereColumn("Shipment ID").matchValue(id)
for(var i=0 ; i < tableWithinId.length() ; i++){
var rangeRowWithinId = tableWithinId.getRow(i);
tableProcessing.addNewEntry(rangeRowWithinId);
for(var cell in rangeRowWithinId.getRange()) cell.setValue("-");
}
//reset value
tableReferenceId.getTableWhereColumn("Move to Sheet").matchValue(1).getWithinColumn("Move to Sheet").cellAtRow(0).setValue(0)
}
See below the app script file you need to create in order to use this utils function:
https://github.com/SolannP/UtilsAppSsript/blob/main/UtilsGSheetTableHelper.gs

Google onEdit Script for multiple columns drop downs

I have some script that allows me to select multiple items from a drop down in google sheets. I have the below script which works fine for column L(12) but I want this to also work on column M(13) and N(14). I have tried || (as in OR) which then makes all the drop downs in the sheet also allow multiple selections which I don't want. Can anyone help me with this?
function onEdit(e) {
var oldValue;
var newValue;
var ss=SpreadsheetApp.getActiveSpreadsheet();
var activeCell = ss.getActiveCell();if(activeCell.getColumn() == 12 && ss.getActiveSheet().getName()=="Client information"){
newValue=e.value;
oldValue=e.oldValue;
if(!e.value) {
activeCell.setValue("");
}
else {
if (!e.oldValue) {
activeCell.setValue(newValue);
}
else {
if(oldValue.indexOf(newValue) <0) {
activeCell.setValue(oldValue+','+newValue);
}
else {
activeCell.setValue(oldValue);
}
}
}
}
}
function myFunction() {
}
You need to make sure that those columns should be within the range of the statement as what dared provided as an answer. column should be within 12 and 14 inclusively.
Additionally, I have made some modifications to make use of the event object instead of SpreadsheetApp and improved your if-else block.
Code Modifications:
function onEdit(e) {
// Spreadsheet object can be retrieved from event via e.source
var sheetName = e.source.getActiveSheet().getName();
// Range object can be retrieved from event via e.range
var range = e.range;
var column = range.getColumn();
if (sheetName == "Client information" && column >= 12 && column <= 14 && e.value) {
var newValue = e.value;
var oldValue = e.oldValue;
// first if statement is the default behavior, no need to include
// the else statement is now combined on the outer if statement
if (!e.oldValue)
range.setValue(newValue);
else {
if (oldValue.indexOf(newValue) < 0)
range.setValue(oldValue + ',' + newValue);
else
range.setValue(oldValue);
}
}
}
Output:
function onEdit(e) {
// cache the column number
let col = e.range.getColumn()
if ( e.source.getActiveSheet().getName() === "Client information" &&
col > 11 && col < 15) {
// This block will run if any of the columns L, M or N are edited
// If you need different behaviour depending on which column was edited test the column number
// Column L
if (col === 11) {
// This block will run if column L is edited
}
}
// Without seeing the type of data you are working with I'm unclear on what
// your old and new values represent.
}

Script to format a range of cells and insert a specific text based on a date in a Google spreadsheet

I have a planner type Google spreadsheet where data added daily by 8-10 users. When I add a date to a cell, I want all the cells in the same row after that date to be formatted and added a text value something like "ENDED".
At the moment I am doing it with conditional formatting and with an ArrayFormula to add the text value. The problem is that for the ArrayFormula to work the cells must be empty and in my sheet the cells they might contain data before the "ENDED" date cell.
Is there a way to do this with a script?.... and if the script can handle also the formatting of the cells that will be the best solution.
Here is my sample file to understand better what I am trying to do...
https://docs.google.com/spreadsheets/d/1QplyEcNu-svYwFq9wvPVEKnsEP1AnrlAkbBxNwEFPXg/edit#gid=2087617521
You can do this with a trigger and a custom function.
Create a new apps script project and use this code:
function onEdit(e) {
if (e.range.getColumn() ==2) {
//User edited the date column
if (typeof e.range.getValue() === typeof new Date()) {
//Value of edit was a date
endColumns(e.range.getRow(), e.range.getValue());
} else if (e.range.getValue() === "" || e.range.getValue() === null) {
var sheets = SpreadsheetApp.getActiveSheet();
var resetRange = sheets.getRange(e.range.getRow(), e.range.getColumn()+1, 1, sheets.getMaxColumns()-e.range.getColumn());
resetRange.clear(); //Will delete all text, not only the "ENDED" text.
}
}
}
function endColumns(rowNum, limitDate) {
var sheets = SpreadsheetApp.getActiveSheet();
var colOffset = 3; //Offset to account for your row Headers
var dateHeader = sheets.getRange(1, colOffset, 1, sheets.getMaxColumns()-colOffset);
var availableDates = dateHeader.getValues()[0];
var foundCol = 0;
for (var i=0; i<availableDates.length; i++) {
if (availableDates[i]>=limitDate) {
break;
}
foundCol++;
}
var rewriteCells = sheets.getRange(rowNum, foundCol+colOffset, 1, sheets.getMaxColumns()-(foundCol+colOffset));
//Add your formatting and text below:
rewriteCells.setValue("Ended");
rewriteCells.setBackground("red");
rewriteCells.setFontColor("yellow");
//Clear all cells that are "white" (no header)
for (var i=0; i<availableDates.length; i++) {
if (availableDates[i]==="" || availableDates[i] ===null) {
sheets.getRange(rowNum, colOffset+i).clear();
}
}
}
Then, create a trigger to run the onEdit function on every edit.
In this case there are some hardcoded values:
e.range.getColumn() == 2 for the row where you add the dates on
var colOffset = 3 for the number of columns to skip before reading the dates
Hope this helps!

Count and display how many times values changed in a column

At the start sorry for my poor english, i will try my best to explain everything.
Is there a way to count how many times value in column D has been changed and show it in column E?
In this example in column D person can select two values from list - "☐" and "☑". In column E i would want to see how many times person changed their selection. Lets say for for E3 i want to see how many time D3 was changed, for E4 to show how many times D4 was change and so on.
Thank you for all help!
Example spreadsheet
The only thing i found was somehitng like that:
function onEdit(e) {
if(e.range.getA1Notation() == "D2") {
var sCounter = e.source.getRange("E2");
var counter = sCounter.getValue();
if(counter === 0) {
counter = 1;
} else {
counter ++;
}
sCounter.setValue(counter);
}
}
The problem is that it only counts one cell and i need it to work for the whole column.
This should do it:
function onEdit(e) {
var ss =SpreadsheetApp.getActiveSpreadsheet()
var s=ss.getActiveSheet()
var editColumn=e.range.getColumn()
var editRow = e.range.getRow()
if(editColumn == 4 && editRow >=2) {
var sCounter = s.getRange(editRow,editColumn+1,1,1);
var counter = sCounter.getValue();
if(counter === 0) {
counter = 1;
} else {
counter ++;
}
sCounter.setValue(counter);
}}
Here is my test spreadsheet you can copy and try:
https://docs.google.com/spreadsheets/d/1I5_NVMKPvqN9J2sSlcLRnQbzoVF4iRKOuXJ5mshb-RY/edit?usp=sharing