Check/Uncheck Boxes From a Dynamic Range Based On Condition - google-apps-script

I have a to-do list with checkboxes for my employees in a spreadsheet with a dynamic number of rows that cannot exceed row 20. Because starting from row 20 another employee's to-do list is available.
I have a checkbox next to the name of the employee. Once I check/uncheck that checkbox, I want all below checkboxes to check or uncheck no matter how many rows are added or removed as long as it does not pass row number 20.
I have therefore named the range on row 20 so that even if it moves, apps script can locate it through named range. I have removed the named range and called it instead ('A20') in the script below to avoid confusion.
I thought a forloop could work but I must be doing something wrong because it is executing successfully but showing no results.
function Todolist() {
var spreadsheet = SpreadsheetApp.getActive();
var lianrange = spreadsheet.getRange('A20');
for (var i = 1 ; i<lianrange.length;i++){
if (spreadsheet.getCurrentCell().getValue() === 'check'){
spreadsheet.getRange[i].setValue('Y');
}
if (spreadsheet.getCurrentCell().getValue() === 'uncheck'){
spreadsheet.getRange[i].setValue('N');
}
}
};
Your help is much appreciated.

Check or uncheckall
function onEdit(e) {
//e.source.toast('Entry');
const sh = e.range.getSheet();
const rg = sh.getRange('stopRow');//stopRow is name of namedRange
const sr = rg.getRow();
//need to update name of sheet
if (sh.getName() == "Sheet0" && e.range.columnStart == 2 && e.range.rowStart < sr) {
//e.source.toast('Gate1');
const vs = sh.getRange(e.range.rowStart, e.range.columnStart, sr - e.range.rowStart + 1).getValues().flat();
const cv = sh.getRange(e.range.rowStart, e.range.columnStart).getValue();
let o = vs.map((e, i) => [cv])
sh.getRange(e.range.rowStart, e.range.columnStart, o.length).setValues(o);
}
}
demo:

Related

Google Sheets: Append then Delete rows based on Tickbox condition

I'm attempting to create a spreadsheet to organise products ordered at my workplace.
When an order is received a team member would add the details to the sheet; when it is collected they'd fill out date and ID then tick the order complete. See Attached
What I want to happen next is that the row containing the complete details from that order is appended to a second page in the sheet and the original row is deleted.
I can't make sense of how to get this to run automatically when the box is checked; so far I have been compiling a script to run from a button press:
function runFiling() {
function moveRows() {
var ss = SpreadsheetApp.getActive();
var osh = ss.getSheetByName('Current');
var dsh = ss.getSheetByName('Collected');
var srg = osh.getDataRange('H2:H');//You might want to specify a more unique range. This just gets all of the data on the sheet
var svA = srg.getValues();
var d=0;//deleted row counter
for(var i=1;i<svA.length;i++) {
if(svA[i][7] =='TRUE') {
dsh.appendRow(svA[i]);//append entire row to Sheet2
osh.deleteRow(i-d+1);//accounts for the difference between length of array and number of remaining row.
d++;
}
}
}
}
However even this fails to Append or Delete anything although no errors are found/returned.
If anyone can suggest a way to fix the above or, preferably, how to make the script work when the box is ticked your help will be most appreciated.
Try it this way using an onEdit(e) function
function onEdit(e) {
const sh = e.range.getSheet();
if (sh.getName() == 'Current' && e.range.columnStart == 7 && e.range.rowStart > 1 && e.value == "TRUE") {
const dsh = ss.getSheetByName('Collected');
const vs = sh.getRange(e.range.rowStart, 1, 1, sh.getLastColumn()).getValues()
dsh.getRange(dsh.getLastRow() + 1, 1, vs.length, vs[0].length).setValues(vs);
sh.deleteRow(e.range.rowStart);
}
}
This will accomplish the task line by line as the check boxes are checked off by the user.

OnEdit trigger fired simultaneously causing wrong calculation of last row

I'm new to Google App Script and trying to create a queue-like spreadsheet.
The current design is that: in sheet A, there's a column of checkboxs, and if any one of them is checked, the entire row would be moved to the end of another sheet, say sheet B.
The way I calculate the end of the sheet B is as followed:
(I read from a different stackoverflow post)
var Avals = dest.getRange("B:B").getValues();
var Alast = Avals.filter(String).length;
var lastRow = String(Alast + 1);
The issue I'm having here is that when I check two checkboxes quickly one by one, they are mapped to the same lastRow in sheet B, because the second checkbox triggers the onEdit trigger before the first one has moved the entire row to the bottom of the sheet B.
Does anyone know how to solve this issue?
Right now, the workaround is since it's a shared google sheet, I asked my colleagues not to click the checkbox column at the same time.
This seems to be working for what little testing I've done
function onEdit(e) {
LockService.getScriptLock().tryLock(2000)
const sh = e.range.getSheet();
if(sh.getName() == "Sheet0" && e.range.columnStart == 1 && e.range.rowStart > 1 && e.value == "TRUE") {
let tsh = e.source.getSheetByName("Sheet1");
let vs = sh.getRange(e.range.rowStart, 1, 1,sh.getLastColumn()).getValues();
tsh.getRange(tsh.getLastRow() + 1, 1 , vs.length, vs[0].length).setValues(vs);
}
LockService.getScriptLock().releaseLock();
}

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

How do I get cells to automatically static timestamp on one sheet when a drop down is changed on different sheet?

I am building a client tracker w/ several functionalities. One sheet ('Current Clients') has all the clients listed by row with columns to capture different data about each client(i.e. point person, origin of the contact, where they are in the client pipeline or "stage" and "status").
On 'Current Clients' Column C is Client Name and Column F is a dependent drop-down with options for each status (aka sub-stage or phase) of the client pipeline.
In sheet 'Client Timeline' sheet I am trying to create a dynamic, visual tracker that logs static timestamps for every time the drop-downs in 'Current Clients'!F2:F (aka Status in client pipeline) are changed.
These timestamps would then trigger cells on the dynamic visual calendar (columns U-BJ on 'Client Timeline' sheet) so that the end result is a gantt-like display of the dates for the beginning and end of each status in the client pipeline.
So far, I have 'Client Timeline'C:C set to filter the names of the clients (from 'Current Clients'C:C) and I have columns F-T on 'Client Timeline' labeled and corresponding to the statuses from the drop-down in 'Current Clients' column F that I need to record timestamps for.
I am feeling reasonably confident in making the dynamic visual calendar section function as I've used this template for gantt charts before.
I need help with writing GAS (or a function) to auto-record static timestamps in the corresponding row and column on 'Client Timeline' sheet every time a clients status is changed in the Status column (F) on the 'Current Clients' sheets.
I have watched several videos and looked at scripts that record static timestamps in a cell when another cell is changes on the same sheet. But I need it to happen between two sheets and I also need it to apply to every cell in the range F2:T34 on the 'Client Timeline' sheet.
I know this is complicated, but I'm hoping someone can help me out!
Here is my template, please make a copy of your own in order to make edit. Please also disregard hidden and protected parts of the spreadsheet, they pertain to other functionalities that I do not need help with at this time. https://docs.google.com/spreadsheets/d/1kf-fVJ3OIir2Hm1nGuTt84fp99G_F0KdTSnuZFM7BJg/edit?usp=sharing
Thank you in advance!
Time Stamp on one sheet for change in another
function onEdit(e) {
const sh = e.range.getSheet();
const validatedSelectionColumn = 1;
if(sh.getName() == 'editted sheet name' && e.range.columnStart == validatedSelectionColumn) {
e.source.getSheetByName(" timestamp sheet name").getRange('range in a1notation for timestamp').setValue(new Date());
}
}
I just did it this way and it works:
function onEdit(e) {
const sh = e.range.getSheet();
if(sh.getName() == 'Sheet0' && e.range.columnStart == 1) {
e.source.getSheetByName("Sheet1").getRange('A1').setValue(new Date());
}
}
You could also do it this way, so that you now you also no what cell was changed
function onEdit(e) {
const sh = e.range.getSheet();
if(sh.getName() == 'Sheet0') {
e.source.getSheetByName("Sheet1").getRange(e.range.rowStart,e.range.columnStart).setValue(new Date());
}
}
You could also put the edits in the same range by storing them in the notes:
function onEdit(e) {
const sh = e.range.getSheet();
if(sh.getName() == 'Sheet0') {
let msg = `Value: ${e.value}\nRange: ${e.range.getA1Notation()}\nTimeStamp: ${new Date()}`;
sh.getRange(e.range.rowStart,e.range.columnStart).setNote(msg);
}
}
This builds a log in notes by appending each edit to the notes and it also provides a toast to let you know that it's completed.
function onEdit(e) {
const sh = e.range.getSheet();
if(sh.getName() == 'Sheet0') {
let old = e.range.getNote();
let msg = `${old}\nValue: ${e.value}\nRange: ${e.range.getA1Notation()}\nTimeStamp: ${new Date()}` ;
e.range.setNote(msg);
e.source.toast('Note Set');
}
}
Workflow:
Get information on which cell was edited via event objects (which column, row, sheet, etc.).
Check that the edited cell is in Current Clients, in column F, and not in the first row.
Get the edited cell value to get the edited status, and the corresponding client in column C of the edited row.
Check the row and column indexes of the corresponding status and client in the target sheet.
Set the timestamp on the corresponding cell (see Date).
Code sample:
const SOURCE_SHEET_NAME = "Current Clients";
const TARGET_SHEET_NAME = "Client Timeline";
const SOURCE_STATUS_COL = 6; // Column F
const SOURCE_CLIENT_COL = 3; // Column C
const TARGET_STATUS_ROW = 6;
const TARGET_CLIENT_COL = 3; // Column C
function onEdit(e) {
const range = e.range;
const sheet = range.getSheet();
const colIndex = range.getColumn();
const rowIndex = range.getRow();
if (sheet.getName() === SOURCE_SHEET_NAME && colIndex === SOURCE_STATUS_COL && rowIndex > 1) {
const sourceStatus = range.getValue();
const sourceClient = sheet.getRange(rowIndex, SOURCE_CLIENT_COL).getValue(); // Column C
const ss = e.source;
const targetSheet = ss.getSheetByName(TARGET_SHEET_NAME);
const targetSheetValues = targetSheet.getDataRange().getValues();
const targetRow = targetSheetValues.findIndex(row => row[TARGET_CLIENT_COL-1] === sourceClient) + 1;
const targetCol = targetSheetValues[TARGET_STATUS_ROW-1].indexOf(sourceStatus) + 1;
const now = new Date();
targetSheet.getRange(targetRow, targetCol).setValue(now);
}
}
Note:
If you have another onEdit function in your script, you'll have to integrate both into a single function (for example, rename this sample's function to onEdit1 and call it inside your main onEdit function).
Consider removing the formulas you have set in the target range so that they don't mess with the script trying to write the timestamps.

Sort checked rows to bottom

I am using google sheets to sort school assignments. I am currently using the below coding to auto-sort the sheet by deadline. I have a checkbox in column F that will mark a row as "0" when it is done (when the box is checked). And "unfinished" when the box is not checked. I would like to include a piece in my auto-sort script that automatically puts rows marked "0" (box is checked) below all rows that are marked unfinished (box is not checked). The code below is all working but I am not sure how to go about adding this feature as I am quite new to coding. Any ideas?
(I've posted this before but it was taken down because it had too little detail, sorry if you saw the first one. This one is better)
function autoSort(e){
const ss = SpreadsheetApp.getActiveSpreadsheet()
const ws = ss.getSheetByName("School Assignments")
const range = ws.getRange(2,1,ws.getLastRow()-1,6)
range.sort({column: 5, ascending: true })
}
function onEdit(e){
const row = e.range.getRow()
const column = e.range.getColumn()
if(!(column === 5 && row >= 2)) return
autoSort(e)
}
Sorts checked items in column F to bottom
function onEdit(e) {
const sh = e.range.getSheet();
if(sh.getName() == "School Assignments" && e.range.columnStart == 6 && e.range.rowStart > 1 && e.value == "TRUE") {
sh.getRange(2,1,sh.getLastRow() -1,sh.getLastColumn()).sort({column:6,ascending:true});
}
}
This assume that the checked value is "TRUE" and the unchecked value is "FALSE"