I have a sheet with rows I'd like to move to another sheet based on a cell value. I tried following this post's solution (refer below), but I'm having trouble editing the script towards what I want it to do.
I'm doing Check-Ins for an event. I would like to be able to change the value in Column F, populate Column G with the time the status changed, and for the row data to migrate to the Attendee Arrived sheet.
I believe the script already does this, but one has to run it manually. It also takes care of deleting the row data in A (Event) after migrating it to B (Attendee Arrived).
My question is could someone please help me set it up in order for script to run continuously (on edit), and also accomplish all of the above if I missed something?
I don't believe the script will respect the drop down format as it runs so I'm willing to manually type in something. It'd be cool if it could stay that way though - makes it easier for one.
Here's the sheet I'm testing on.
https://docs.google.com/spreadsheets/d/1HrFnV2gFKj1vkw_UpJN4tstHVPK6Y8XhHCIyna9TLJg/edit#gid=1517587380
Here's the solution I tried following. All credit to Jason P and Ritz for this.
Google App Script - Google Spreadsheets Move Row based on cell value efficiently
Thank you D:
function CheckIn() {
// How Many Columns over to copy
var columsCopyCount = 7; // A=1 B=2 C=3 ....
// What Column to Monitor
var columnsToMonitor = 6; // A=1 B=2 C=3 ....
//TARGET SPREAD SHEETS
var target1 = "Attendee Arrived";
//Target Value
var cellvalue = "Attendee Arrived";
//SOURCE SPREAD SHEET
var ss = SpreadsheetApp.openById('1HrFnV2gFKj1vkw_UpJN4tstHVPK6Y8XhHCIyna9TLJg');
var sourceSpreadSheetSheetID = ss.getSheetByName("Event");
var sourceSpreadSheetSheetID1 = ss.getSheetByName(target1);
var data = sourceSpreadSheetSheetID.getRange(2, 1, sourceSpreadSheetSheetID.getLastRow() - 1, sourceSpreadSheetSheetID.getLastColumn()).getValues();
var attendee = [];
for (var i = 0; i < data.length; i++) {
var rValue = data[i][6];
if (rValue == cellvalue) {
attendee.push(data[i]);
} else { //Fail Safe
attendee.push(data[i]);
}
}
if(attendee.length > 0){
sourceSpreadSheetSheetID1.getRange(sourceSpreadSheetSheetID1.getLastRow() + 1,
1, attendee.length, attendee[0].length).setValues(attendee);
}
//Will delete the rows of importdata once the data is copided to other
sheets
sourceSpreadSheetSheetID.deleteRows(2,
sourceSpreadSheetSheetID.getLastRow() - 1);
}
Try this:
I imagine that you already know that you'll need an installable onEdit Trigger and that you can't test a function of this nature by running it without the event object.
function checkIn(e) {
var sh=e.range.getSheet();
if(sh.getName()!="Event") return;
if(e.range.columnStart==6) {
if(e.value=="Attendee Arrived"){
e.range.offset(0,1).setValue(Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "M/d/yyyy HH:mm:ss"));
var row=sh.getRange(e.range.rowStart,1,1,sh.getLastColumn()).getValues()[0];
e.source.getSheetByName("Attendee Arrived").appendRow(row);
sh.deleteRow(e.range.rowStart);
}
}
}
Related
I have a google sheet with a list of job names in column F. Each job name can appear multiple times in the list and I need to count or add a unique ID into another column in the same row for every new Jobname entered in column F.
The caveat is, the entries in column F can be "penciled" meaning a value will be entered before it is added to the dropdown list that validates column F (a named range called "JobList"). Those entries should not have a unique ID added.
I've seen a few different solutions online and wondering what could be the best option for my requirements to proceed with. Linked below
Automatically generate a unique sequential ID in Google Sheets
https://yagisanatode.com/2019/02/09/google-apps-script-create-custom-unique-ids-in-google-sheets/
Apologies if we are not allowed to link other sites, I will remove immediately if so.
Currently, I'm thinking of building this solution
Function1 to return number of times jobname appears in Col F
Function2 to add +1 to Function1 (unless its 1) and append to row
onEdit(e) > update this function to call Function2 when Col F is edited and send parameters of current row and value (jobname)
Seems like it could work - will come back with my progress
---- UPDATE ---
I have made a function that seems to work, it does take about 8 seconds to run, however, and seems like it could be prone to issues on many edits, but any onEdit function I think would behave in a similar way.
function onEdit(e) {
var range = e.range;
var ss = e.source;
var sheet = ss.getActiveSheet().getName();
var row = range.getRow();
var col = range.getColumn();
var jobname = e.value;
var count = 0;
var job = jobname;
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Calendar v2");
var joblist = sh.getRange("F:F").getValues();
if(sheet=="Calendar v2" && col==6){
//range.setNote(sheet+' + '+row+' + '+col+' + '+jobname+ 'Last modified: ' + new Date());
// count = countJobname();
for (var i=0;i<joblist.length;i++){
if(joblist[i]==job){
count = count + 1;
Logger.log(count);
} SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheet).getRange(row,15).setValue("TRIGGER "+count);
}
}
}
I did build out working separate functions but for some reason, they were not working when called in onEdit, I rebuilt the onEdit function line by line and it eventually worked, will rework the functions back in but still open to suggestions if anyone has a better method. Still a rookie at this stuff :)
Another UPDATE
the last setValue line was running inside the for loop, badly formatted code introduces the dumbest problems. it runs almost instantly now!
Here is the revised code, it's a pretty simple solution and hits the core functionality and I will be building it out further to provide unique ID codes based off 2 columnns (Jobname + Category).
I haven't added the check for "pencil" entries, currently it checks all values in column F.
To implement that check instead we would add the commented code instead in the countJobname function find the Named Range instead
function onEdit(e) {
var range = e.range;
var ss = e.source;
var sheet = ss.getActiveSheet().getName();
var row = range.getRow();
var col = range.getColumn();
var jobname = e.value;
var count = 0;
if(sheet=="Calendar v2" && col==6){
count = countJobname(jobname);
SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheet).getRange(row,15).setValue("TRIGGER "+count);
}
}
function countJobname(jobname){
var job = jobname;
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Calendar v2");
var joblist = sheet.getRange("F:F").getValues(); //.getRangeByName("JobList").getValues();
var count = 0;
for (var i=0;i<joblist.length;i++){
if(joblist[i]==job){
count = count + 1;
Logger.log(count);
}
}return count
}
I am trying to get this to run automatically, however with the current form it only works if i manually run the code.
I have another code file in this sheet which also uses onEdit, how can I incorporate the same onEdit to this code or make it run whenever column G is selected "Complete" to then change column H to "shipped"
function addValidation() {
// Get the spreadsheet and active sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
// Get the data as an Array you can iterate and manipulate
var data = sheet.getDataRange().getValues();
var rule = SpreadsheetApp.newDataValidation().requireValueInList(["Complete"]).build();
// Loop the sheet
for(var i=0; i<data.length; i++) {
if(data[i][11] == "Complete") {
sheet.getRange(i+1, 8).clear().setDataValidation(rule);
} {
sheet.getRange(i+1, 8).clearDataValidations().setValue("Shipped");
}
}
}
A couple things:
In your if statement, you're not checking Column G - you're checking column K (11). If you want to check G, you need to change your if to:
if(data[i][6].toString() == "Complete") {
...
}
Your logic is backward and you're missing the else keyword to run the alternative. As is, it will mark 'Shipped' if it doesn't match 'Complete.' Swap the statements to fill the cell values correctly.
Also, string checking in a loop is a little tricky. Cell references as indices are objects, not strings. You need to convert your cell using the .toString() method before you can compare a straight string.
Your whole script should read:
function addValidation() {
// Get the spreadsheet and active sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
// Get the data as an Array you can iterate and manipulate
var data = sheet.getDataRange().getValues();
// Loop the sheet
for(var i=0; i<data.length; i++) {
// To change which columns you're testing, change the second value.
// ColG = 6, not 11.
// Convert data[i][6] using .toString() to compare the value
if(data[i][6].toString() == "Complete") {
// If it's "Complete," mark Col H as 'Shipped'
sheet.getRange(i+1, 8).setValue("Shipped");
} else {
// If you want something else to happen, add that here.
}
}
}
The last step is to enable a trigger for the script. If you name your function onEdit(), it will run by itself because editing is a simple script. Since it's called addValidation, you need to manually add the onEdit trigger in the UI. Go to Edit > Current Project Triggers and click on "Add Trigger" in the bottom right. Follow the steps in the UI and the script should run itself.
I am completely new in writing scripts for google sheets, so I was hoping some of you could help/guide me a little bit.
So Ideally, I want a script to clear (not remove) ALL filters in my sheet. This is, however, complicated for me to do (If some of you have such a script, I would LOVE to see it :) )
Instead, I made this one (Used recorder):
function Clear_Filter() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A5').activate();
spreadsheet.getActiveSheet().getFilter().removeColumnFilterCriteria(1);
spreadsheet.getRange('B5').activate();
spreadsheet.getActiveSheet().getFilter().removeColumnFilterCriteria(2);
spreadsheet.getRange('C5').activate();
spreadsheet.getActiveSheet().getFilter().removeColumnFilterCriteria(3);
spreadsheet.getRange('G5').activate();
spreadsheet.getActiveSheet().getFilter().removeColumnFilterCriteria(7);
spreadsheet.getRange('J5').activate();
spreadsheet.getActiveSheet().getFilter().removeColumnFilterCriteria(10);
spreadsheet.getRange('M5').activate();
spreadsheet.getActiveSheet().getFilter().removeColumnFilterCriteria(13);
};
So my filter is set in Row 5. First I made the above for all columns (I had 20), but the problem is, that the code is very slow :( So now I am using the columns, that I use the most, when filtering, but the code is still slow. Well the worst thing is, that the code is running one column at a time (which we see in the code), and when the code is finish, I end up in the last column.
Can I do something? I dont want my sheet window keep turning right, when I run the code, and then end up in column M.
I will appreciate any help!
Thanks
Here is mine. The function does not remove filters. Instead, it clears them as requested.
function clearFilter(sheet) {
sheet = SpreadsheetApp.getActiveSheet(); //for testing purpose only
var filter = sheet.getFilter();
if (filter !== null) { // tests if there is a filter applied
var range = filter.getRange(); // prevents exception in case the filter is not applied to all columns
var firstColumn = range.getColumn();
var lastColumn = range.getLastColumn();
for (var i = firstColumn; i < lastColumn; i++) {
filter.removeColumnFilterCriteria(i);
}
Logger.log('All filters cleared')
}
else {Logger.log('There is no filter')}
}
Reset filters criterea + sort by first column (as default state).
And add this action to main menu.
/** #OnlyCurrentDoc */
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [
{name: "Reset filters", functionName: "ResetFilters"},
];
ss.addMenu("Custom actions", menuEntries); // add to main menu
}
function ResetFilters() {
var spreadsheet = SpreadsheetApp.getActive();
var lastColumn = spreadsheet.getActiveSheet().getLastColumn();
var filter = spreadsheet.getActiveSheet().getFilter();
var criteria = SpreadsheetApp.newFilterCriteria().build();
for (var i = 1; i <= lastColumn; i++) {
filter.setColumnFilterCriteria(i, criteria);
}
filter.sort(1, true); // remove this line for disable setting of sorting order
};
To clear all
`function turnOffFilter(sheet) {
for (var index = 1; index < sheet.getLastColumn(); index++) {
if (sheet.getFilter().getColumnFilterCriteria(index)) {
sheet.getFilter().removeColumnFilterCriteria(index);
}
}
}`
It seems that the answers (e.g. proposed by Birmin) work fine but the script is painfully slow. I find it much faster to reapply the filter:
function clearFilter(sheet) {
sheet = SpreadsheetApp.getActiveSheet(); //for testing purpose only
var filter = sheet.getFilter();
if (filter !== null) { // tests if there is a filter applied
var range = filter.getRange();
filter.remove();
range.createFilter();
Logger.log('All filters cleared')
}
else {Logger.log('There is no filter')}
}
I, have you tried :
function Clear_Filter() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getFilter().remove();
}
I'm making a task manager in Google Sheets that when a task is created it sends the task that was just made to a target Sheet, I was able to get it to send the data just fine to the target sheet but my problem lies with what i wanted to do with that edit made on the target sheet. On the target sheet I want it to grab that info that was just inputted and place it in the next available row, but when I test my code It doesn't wanna work correctly.
My Code(for the target sheet):
function onEdit (e) {
var ss = SpreadsheetApp.openById('SheetIdHere');
var sheet = ss.getSheets()[0];
var sheetRows = sheet.getMaxRows();
var openRow = 0;
for (var i =4;i < sheetRows;i++) {
var currentValue = sheet.getRange(i,2).getValue();
if (currentValue == "") {
openRow = i;
break;
}
}
var lastEdit = sheet.getRange(1000, 2, 1, 3);
var lastEditValues = lastEdit.getValues();
var openRange = sheet.getRange(openRow, 2, 1, 3);
var openA1 = openRange.getA1Notation();
sheet.getRange(openA1).setValues(lastEditValues);
}
So far from my tests, the for loop to find the next available row is working fine because when I make another change on the sheet afterward it will grab the edited information fine and input it into the next row, but on the initial onEdit fire it will just grab the values from the previous test at row 1000 and put it into the next row instead of the new values.
I feel like there is a mistiming perhaps on when it grabs the values because if I clear the sheets and send a new task nothing appears on the next available row but I might be mistaken on that feeling.
Im trying to create a simple script that firstly checks (all cells in row 3 starting from column 3) for whether they contain a name different from the available sheets and if so create a new one. If not go to the next cell down. Preferably until the row is empty but I didnt get that far. Currently I have
var row = 3; //Global Variable
function Main() { // Main Function
Activate();
GetNames();
}
function Activate() { // Initialize
var get = SpreadsheetApp.getActiveSpreadsheet();
var import = get.getSheetByName("Import");
}
function GetNames() { // Get Names and check for existing Sheets
var ss = SpreadsheetApp.getActiveSpreadsheet();
var import = ss.getSheetByName("Import");
var sheet = ss.getSheets()[0];
for (i = 0; i < 1000; i++) { // Loop which I think is broken
var names = import.getRange(row,3).getValue();
if (import.getSheetByName(names) == null) {
import.insertSheet(names);
import.activate();
}
row + 1;
}
}
And here is the Data
It succeeds to add the first sheet but fails to continue in the loop I think.
As you will probably see I'm very new to this and any help would be appreciated.
You probably wanted to increase row by 1 here:
row + 1;
But you're not. row + 1 is just an expression with a value (4, in your example, because row remains 3 throughout). What you would need is the statement
row = row + 1;
But if this is all that you're using the global variable row for, you don't need it at all. Just use the loop variable i that's already counting from 0 to 1000. You probably want something like import.getRange(i+3,3).