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
Related
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.
My script works perfectly when I execute it manually through the editor but fails to fire with the edit or change trigger.
I saw a bunch of people having the same problem but none of these solutions help me solving this issue.
Has anybody a solution for this ?
function createNewSheets() {
// 1. Retrieve the current sheet names.
var dataSheet = SpreadsheetApp.getActiveSpreadsheet();
var sheetNames = dataSheet.getSheets().map(s => s.getSheetName());
// 2. Retrieve the values from "mastersheet" sheet.
var masterSheet = dataSheet.getSheetByName('Liste de nageurs');
var values = masterSheet.getRange('A2:B' + masterSheet.getLastRow()).getValues();
// 3. Using the retrieved sheet names and values, the new sheets are inserted and the values are put.
values.forEach(r => {
if (!sheetNames.includes(r[0])) {
var newSheet = dataSheet.insertSheet(r[0])
sheetNames.push(r[0]);
newSheet.appendRow(r);
}
});
}
I ran it this way, using a checkbox in A1:
function onEdit(e) {
//e.source.toast('entry');//just a toast to let me know it's work
const sh = e.range.getSheet();// getting sheet name
//next line limits execution to Sheet1 A1 being changed to 'TRUE'
if (sh.getName() == 'Sheet1' && e.range.columnStart == 1 && e.range.rowStart == 1 && e.value == 'TRUE') {
e.range.setValue('FALSE');//reset the checkbox back to 'FALSE'
//the rest is your code
var ss = SpreadsheetApp.getActive();
var names = ss.getSheets().map(s => s.getSheetName());
var masterSheet = ss.getSheetByName('Sheet2');//changed sheet name
var vs = masterSheet.getRange('A2:B' + masterSheet.getLastRow()).getValues();
vs.forEach(r => {
if (!names.includes(r[0])) {
var newSheet = ss.insertSheet(r[0])
names.push(r[0]);
newSheet.appendRow(r);
}
});
}
}
You can simplify your code if you learn how to use the event object better but I left most of your code intact. The problem that most users dislike about using the event object is that you can't debug the code by running it from the script editor because you have to supply the event object as a parameter. I normally debug them but running them from the trigger. You can use JSON.stringify(e) to look at the event object.
Using the event object normally results in faster code and simple triggers must finish in 30 seconds or less.
First of all, I ask you to excuse me if I make some language-mistake (I'm Italian!).
I'm trying to write a script for a Google Sheet that can help me to track the number of changes of a column. I would like a counter that grows everytime a value of a cell changes. Ex:
-Column A: the cell A3 changes from "2020" to "2021"
-Column B: the cell B3 changes from 0 to 1 (o from 2 to 3, a simple +1 on the value).
I wrote this code but I cannot understand where is the error.
function onEdit(e) {
incrementCounter_(e);
}
function incrementCounter_(e) {
var stw = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Foglio2");
var c1 = stw.getRange(2, 1);
var c2 = stw.getRange(2, 2);
if (!e || !e.range) {
return;
}
var sheet = e.range.getSheet();
for (var i=2; i<=6; i++){
if (sheet.getName() === stw && e.range.getA1Notation() === stw.getrange().getvalue(i,1)) {
var cell = stw.getrange().getvalue(i,2);
cell.setValue((Number(cell.getValue()) || 0) + 1);
}
}
}
Thanks for the help!
There is no need to use two functions for this.
Code
// Copyright 2020 Google LLC.
// SPDX-License-Identifier: Apache-2.0
function onEdit(e) {
var sheet = e.range.getSheet();
var sheetName = sheet.getName();
if (sheetName == "Foglio2" && e.range.getColumn() == 1) {
var row = e.range.getRow();
var val = sheet.getRange(row, 2).getValue();
sheet.getRange(row, 2).setValue(val + 1);
}
}
Explanation
What you want can be achieved by using the onEdit(e) trigger. The above function makes use of the e event object and checks where exactly the edit is being made. As it can be seen, the for loop is not needed in this situation as in order to get the column and row needed the getColumn() and getRow() methods have been used.
In this situation, the script checks if the sheet in which the edit has been made is Foglio2 and if an edit has been made on the A column. If the condition checks, it increments the corresponding value from the B column.
Note
Please note that getValue(value1, value2) is not a valid method and if you want to get the value for that specific range, you must pass the two parameters to the getRange() method.
Reference
Apps Script Event Objects;
Apps Script Sheet Class;
Apps Script Range Class - getValue().
Do you mean this?
function onEdit(e) {
if (e.range.getA1Notation() === 'A3') {
var range = SpreadsheetApp.getActiveSheet().getRange('B3');
var value = range.getValue();
value++;
range.setValue(value);
}
}
I have the following code to show or hide columns/rows in a sheet based on the contents of the first row/column (whether it's more than or less than 0) which works fine when called from an installable trigger, but as an onEdit function, it doesn't work.
Ideally, I'd like to avoid using installable triggers, as that doesn't copy with the sheet and it's primarily intended as a template to be repeatedly copied.
The data is held in the sheet "input" and then provides data for queries in the sheet "table" (which is protected); ideally, it should either run on edit (on the sheet input) or on a one-minute interval (which is the preferable option, but not if it involves using the triggers program as outlined above).
The current version as below doesn't create any error messages but doesn't function as intended either.
function onEdit(){
hideEmptyRows();
hideCols();
}
function hideCols() {
var sh = SpreadsheetApp.getActive().getSheetByName('table');
var totalsRow = 1;
sh.getRange(totalsRow, 3, 1, sh.getMaxColumns()-3).getValues()[0]
.forEach(function(el, i) {
if (!el || el >= 1) sh.showColumns(i + 3)
});
sh.getRange(totalsRow, 3, 1, sh.getMaxColumns()-3).getValues()[0]
.forEach(function(el, i) {
if (!el || el <= 0) sh.hideColumns(i + 3)
})
}
function hideEmptyRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName("Table"); // Enter sheet name
var row = s.getRange('A:A').getValues(); // Enter
column letter that has the text "hide" and "unhide"
s.showRows(1, s.getMaxRows());
for(var i=0; i< row.length; i++){s.showRows(i+1, 1); if(row[i] == 0) { s.hideRows(i+1, 1); } // Value to hide
}}
Explanation:
Your code should be working fine.
However, it is suggested to take advantage of the event object e. In your current solution, if you make any changes to another sheet, onEdit might alter the sheet table. Maybe this is a desired behaviour but I wouldn't recommend it.
Another improvement you can make in this particular project, using the event object, is to execute the forEach part when there is a change in the first row. Namely, there is no need to execute a for loop that performs calculations for row 1, if for example, row 2 is edited.
Solution:
Here I offer you a more maintainable/futureproof solution:
function onEdit(e) {
const sheet_name = 'table';
const row = e.range.getRow();
const col = e.range.getColumn();
const sh = e.source.getActiveSheet();
const totalsRow = 1;
if(sh.getName() == sheet_name && row == totalsRow ){
sh.getRange(totalsRow, 3, 1, sh.getMaxColumns()-3).getValues()[0]
.forEach(function(el, i) {
if (!el || el <= 0) sh.hideColumns(i + 3);
if (!el || el > 0) sh.showColumns(i + 3);
})
}
}
I have written a script in google sheets that will change the time stamp in the selected cell anytime the entire spreadsheet is updated at all. However, I need the timestamp to only update when one single sheet(tab) is updated. Here is my current function:
function onEdit() {
var sheetActive = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName("Board")
.getRange('A28')
.setValue(new Date());
}
The name of the sheet(tab) that I want to check for an update in is "Input" and the specific cell in the tab is A1 if that matters.
The onEdit trigger triggers whenever any sheet/tab is edited. It cannot be restricted to a single sheet. However, even if it is triggered, it is possible to make it do nothing with a simple if and testing the event object that is sent on each edit, which contains the circumstances of the edit made.
Sample:
/**
* #param {GoogleAppsScript.Events.SheetsOnEdit} param1
*/
function onEdit({ range: /*edited range*/ eRange }) {
const restrictCell = 'A1',
restrictToSheet = 'Input';
if (
eRange.getA1Notation() !== restrictCell ||
eRange.getSheet().getName() !== restrictToSheet
)
return;
SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Board')
.getRange('A28')
.setValue(new Date());
}
This is what you are looking for :
function onEdit(e) {
var row = e.range.getRow();
var col = e.range.getColumn();
if ( e.source.getActiveSheet().getName() === "Input" && (row==1 && col==1) ){
e.source.getSheetByName('Board').getRange('A28').setValue(new Date())
}
}