I have a google spreadsheet that i have one last problem i cant seem to solve.
i added a button to this script, and when i press the button it triggers the AddClient function.
How can i make the script below loop down all rows in column 3 searching for the yes value, when it finds it, copy the row below it to sheet "client" and then stop?
function AddClient(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "SETUP" && r.getColumn() == 3 && r.getValue() == "yes") {
var row = r.getRow() + 1; // Add 1 to the active row
var targetSheet = ss.getSheetByName("client");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 2, 1, 4).copyTo(target, {contentsOnly:true}); //Only selecting column 2, then the following 4 columns
}
}
Edit*
Example document:
https://docs.google.com/spreadsheets/d/1DFbAp0IN8_UFv9u8kWiZBTxDogj4e7FnuAPzC0grDw0/edit?usp=sharing
Any help greatly appreciated!
Cheers,
Reiel
Since you have a static form the position of the informatin to be copied will not change
Since we know we want to copy over the data we won't need to do any validation of where we are so all of that stuff can go
Sheets have an appendRow method that take care of the bookkeeping involved with finding the last row
This allows us to simplify the script to the following:
function AddClient() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getSheetByName("SETUP").getRange(25, 2, 1, 4).getValues()[0];
ss.getSheetByName("client").appendRow(data);
}
Edit:
To remove duplicates you could do the following:
function AddClient() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getSheetByName("SETUP").getRange(25, 2, 1, 4).getValues()[0];
var clients = ss.getSheetByName("client").getDataRange().getValues();
if (!clients.filter(function(row) {return data.join("|") === row.join("|");})) {
ss.getSheetByName("client").appendRow(data);
}
}
Note that for the particular example there are some problems because the leading zero gets cut off. Sheets is a bit weird and sometimes tries to force the format to be a number even when you set the cells' formats to Text...
Related
I'm completely new to Java Scipt although over the years I've messed around with VBA & Macros in Excel. I am now using Google Sheets almost exclusively, hence needing to learn Java.
I'm trying to jump to a specific cell (E5) if the current cell is (C14). This is what I've put together so far using scripts 'borrrowed' from others.
ie On entry of data in Cell C13 and pressing Enter, focus goes to Cell C14. The next data is to go into Cell E5.
function onSelectionChange(e) {
var sheetNames = ["Score Input"]; // Set the sheet name.
var ranges = ["C14"]; // Set the range to run the script.
var range = e.range;
var sheet = range.getSheet();
var check = ranges.some(r => {
var rng = sheet.getRange(r);
var rowStart = rng.getRow();
var rowEnd = rowStart + rng.getNumRows();
var colStart = rng.getColumn();
var colEnd = colStart + rng.getNumColumns();
return (range.rowStart >= rowStart && range.rowEnd < rowEnd && range.columnStart >= colStart && range.columnEnd < colEnd);
});
if (check) {
jumpToDetails();
}
};
function jumpToDetails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Score Input");
var goToRange = sheet.getRange('c14').getValue();
//sheet.getRange(goToRange).activate();
SpreadsheetApp.getActive().getRange('E5').activate();
}
It worked, or did until I inserted a row in the sheet, and even though I have changed the associated cell addresses, it now doesn't work?
Two questions. 'Why has it stopped working'? and 'Is there a simpler way to do it'?
I prefer using onEdit(e) on range C13 and jump to E5
For instance here is a script that jump from one cell to the following in addresses'list
function onEdit(event){
var sh = event.source.getActiveSheet();
var rng = event.source.getActiveRange();
if (sh.getName() == 'Score Input'){ // adapt
var addresses = ["C13","E5","E10","H10","E13","H13","E16"]; // adapt
var values = addresses.join().split(",");
var item = values.indexOf(rng.getA1Notation());
if (item < addresses.length - 1){
sh.setActiveSelection(addresses[item + 1]); // except last one
}
}
}
if you want to be able to add rows and columns, play with named ranges (for instance ranges names 'first', 'second, 'third')
function onEdit(event){
var sh = event.source.getActiveSheet();
var rng = event.source.getActiveRange();
if (sh.getName() == 'Score Input'){
var addresses = ["first","second","third"];
var values = addresses
.map(ad => myGetRangeByName(ad))
.join().split(",");
var item = values.indexOf(rng.getA1Notation());
if (item < addresses.length - 1){
sh.setActiveSelection(addresses[item + 1]); // except last one
}
}
}
function myGetRangeByName(n) {
return SpreadsheetApp.getActiveSpreadsheet().getRangeByName(n).getA1Notation();
}
reference
class NamedRange
I am using a modified version of this code. Where the goal is to copy/move a row to another sheet based on a dropdown value. I addition when the dropdown value is changed in sheet 2, after the row has been moved the row should be copied back to sheet 1.
I do have some working code, but it breaks the Don't repeat yourself principle, because the code is repeating it self multiple times. That's why I tried to create a custom function moveWhenDone(); where only the targetSheet variable is unique and is passed as a argument.
For some reason, this code does not work when put in the function and I don't understand why?
Another issue I am facing is regarding the sortSheets(); function, both sheet 1 and sheet 2 have a filter on range A:J sorted by column 1. After the row is copied I need the sheets to re-sort to reflect the changes. For some reason this ONLY works on sheet 1, after I edit a cell in the sheet 1. For sheet 2, no sorting, even after a cell edit.
How can both the sheets be re-sorted after the row is copied?
UPDATE
Link to test sheet
function onEdit(event) {
// assumes source data in sheet 1 named ASSIGMNMENTS
// target sheet 2 of move to named DONE
// getColumn with check-boxes is currently set to colu 7
var act = SpreadsheetApp.getActiveSpreadsheet();
var src = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(src.getName() == "ASSIGMNMENTS" && r.getColumn() == 7 && r.getValue() == "Done") {
// Working but breaks the "Don't repeat yourself" principle
/*var row = r.getRow();
var numColumns = src.getLastColumn();
var targetSheet = act.getSheetByName("DONE");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
src.getRange(row, 1, 1, numColumns).copyTo(target);
src.deleteRow(row);*/
var targetSheet = act.getSheetByName("DONE");
moveWhenDone(targetSheet);
} else if(src.getName() == "DONE" && r.getColumn() == 7 && r.getValue() != "Done") {
// Working but breaks the "Don't repeat yourself" principle
/*var row = r.getRow();
var numColumns = src.getLastColumn();
var targetSheet = act.getSheetByName("ASSIGMNMENTS");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
src.getRange(row, 1, 1, numColumns).copyTo(target);
src.deleteRow(row);*/
var targetSheet = act.getSheetByName("ASSIGMNMENTS");
moveWhenDone(targetSheet);
}
sortSheets();
}
// Only work on ASSIGMNMENTS (sheet 1)
function sortSheets() {
SpreadsheetApp.flush();
var acticeSheet = SpreadsheetApp.getActiveSpreadsheet();
acticeSheet.getRange("A2:Z").sort([{column: 1, ascending: true}]);
}
// Do not work when called from onEdit
function moveWhenDone(targetSheet) {
var source = event.source.getActiveSheet();
var row = r.getRow();
var numColumns = source.getLastColumn();
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
source.getRange(row, 1, 1, numColumns).copyTo(target);
source.deleteRow(row);
}
Here are the issues why your code is not working:
You are using event and r variables in your moveWhenDone() function which is not defined. event and r variables are only accessible in onEdit() unless you pass it as an argument to your moveWhenDone()
The reason why your sort function only works on the active sheet is because you used SpreadsheetApp.getActiveSpreadsheet(); which returns a Spreadsheet object. When getRange() is used, it will get the range on the active sheet in your spreadsheet unless you provide the sheet name as part of your A1 Notation (example, "Done!A2:Z")
By the way, I didn't get your point when you mentioned "Don't repeat yourself principle". It somehow contradicts in your statement where "when the dropdown value is changed in sheet 2, after the row has been moved the row should be copied back to sheet 1."
Here is a sample code to move row data from sheet1 to sheet2 and vise-versa including the sorting:
function onEdit(event) {
// assumes source data in sheet 1 named ASSIGMNMENTS
// target sheet 2 of move to named DONE
// getColumn with check-boxes is currently set to colu 7
var act = SpreadsheetApp.getActiveSpreadsheet();
var src = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(src.getName() == "ASSIGMNMENTS" && r.getColumn() == 7 && r.getValue() == "Done") {
// Working but breaks the "Don't repeat yourself" principle
var row = r.getRow();
var numColumns = src.getLastColumn();
var targetSheet = act.getSheetByName("DONE");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
src.getRange(row, 1, 1, numColumns).copyTo(target);
src.deleteRow(row);
sortSheets()
} else if(src.getName() == "DONE" && r.getColumn() == 7 && r.getValue() != "Done") {
// Working but breaks the "Don't repeat yourself" principle
var row = r.getRow();
var numColumns = src.getLastColumn();
var targetSheet = act.getSheetByName("ASSIGMNMENTS");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
src.getRange(row, 1, 1, numColumns).copyTo(target);
src.deleteRow(row);
sortSheets()
}
}
function sortSheets() {
SpreadsheetApp.flush();
var sheetNames = ["ASSIGMNMENTS", "DONE"];
sheetNames.forEach(sheet => {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheet);
sheet.getRange("A2:H").sort([{column: 1, ascending: true}]);
});
}
Modifications done:
I removed moveWhenDone() since I didn't fully understood its purpose to prevent "Don't repeat yourself principle"
Instead of sorting the sheets every time the onEdit() is triggered, I put the sort function when a row was moved to a different sheet.
I modified the sortSheets() to loop specific sheets listed in sheetNames variable. I changed the range to "A2:H" because the sample sheet provided has mismatch in number of columns. (ASSIGNMENTS sheet has columns A-Y, while DONE sheet has columns A-M, Hence range "A2:Z" cannot be used)
Sample Output:
If you really want to make moveWhenDone() work, you can pass the event and r variables in onEdit(). Sample moveWhenDone(targetSheet, event, r).
But keep in mind that if you do that, what will happen is that you will basically move the current row twice to your destination sheet.
Example:
If you changed Assignment 2 to "Done", its succeeding row will also be moved to another sheet. Hence both Assignment 2 and 5 will be moved even if Assignment 5 is not yet "Done"
I have been using the following script to move "Finished" columns from one sheet to another:
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "Sheet1" && r.getColumn() == 15 && r.getValue() ==
"Finished") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Finished");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);
}
}
Im trying to figure out how to get the script to run even if the sheet has not been opened or edited. Im just not sure how to go about changing it to use a time trigger every minute or so.
There are two aspects to the change to a timed trigger from onEdit.
The first concerns revisions to the code, the second is the trigger details.
Code
The code can't be re-used because the timed trigger doesn't provide the same event details as OnEdit.
In addition, it is possible that several rows might be tagged "Finished" between each trigger event, and the code needs to respond to them all. lastly, each "finished" row can't be deleted as it is found, because this affects the row number of all remaining rows in the column.
The following code would do the job:
Most of it will be familiar to the questioner. The main except is to keep a record of each row number that is moved to "Finished". This is done by pushing the row number onto an array. Then after all data has been examined and moved, there is a small loop that takes the row numbers recorded in the array and deletes the relevant row. The loop works from the highest row number to the lowest; this is so that the deletion of a row does not affect the row number of any remaining rows to be deleted.
function so_53305432() {
// set up the spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// identify source and target sheets
var sourceSheet = ss.getSheetByName("Sheet1");
var targetSheet = ss.getSheetByName("Finished");
// get some variables to use as ranges
var sourcelastRow = sourceSheet.getLastRow();
var numColumns = sourceSheet.getLastColumn();
var targetLastRow = targetSheet.getLastRow();
// get data from the Source sheet
var sourceData = sourceSheet.getRange(1, 1, sourcelastRow, numColumns).getValues();
// set up some variables
var finishedRows = [];
var i = 0;
var x = 0;
var temp = 0;
// loop through column 15 (O) checking for value = "Finished"
for (i = 0; i < sourcelastRow; i++) {
// If value = Finished
if (sourceData[i][14] == "Finished") {
// define the target range and move the source row
var targetLastRow = targetSheet.getLastRow();
var target = targetSheet.getRange(targetLastRow + 1, 1);
sourceSheet.getRange(+i + 1, 1, 1, numColumns).moveTo(target);
// keep track of the source row number.
finishedRows.push(i);
}
}
// set up variables for loop though the rows to be deleted
var finishedLength = finishedRows.length;
var startcount = finishedLength - 1
// loop throught the array to delete rows; start with the highest row# first
for (x = startcount; x > -1; x--) {
// get the row number for the script
temp = +finishedRows[x] + 1;
// delete the row
sourceSheet.deleteRow(temp);
}
}
Trigger
The trigger needs to be revised. To do this:
1) Open the script editor, select Current Project Triggers. OnEdit should appear as an existing trigger with an event type of OnEdit.
2) Change "Choose which function to run" to the new function,
3) Change "Select Event Source" from Spreadsheet to "Time Driven".
4) Select "Type of time based trigger" = "Minutes Timer".
5) Select "Select Minute Interval" = , and select a time period and interval.
6) Save the trigger, and then close the Trigger tab
If "Every Minute" is found to be too often, then the Questioner could try "Every 5 minutes".
I have looked for an answer for this question, but I am exceptionally green to even rudimentary scripting so I have not been able to understand what I have found.
I have a spreadsheet we are using for a worklist - it is separated into three tabs: Samples / Images / Archive
Users access the spreadsheet to collect items to work - once it is complete they mark Column A as "Complete", and I have code very helpfully provided by ScampMichael to automatically move the row to the "Archive" sheet once they do so:
function onEdit(event) {
// assumes source data in sheet named Samples
// target sheet of move to named Archive
// test column with Completed is col 1 or A
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "Samples" && r.getColumn() == 1 && r.getValue() == "Complete") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Archive");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);
}
}
My challenge is that I can not get this code to work simultaneously for both the "Samples" and the "Images" tab. I gather that this is because you can not have more than one onEdit function per spreadsheet, but so far my efforts to expand the code to look at both tabs has failed.
Any help that can be provided is extremely appreciated!
It is still not exactly clear what you want to do, but I took a shot at it. I assumed the image is on the same row number as the completed row on samples and when copying the image to archive I append it to the end of the row created in the archive. I made some changes to your code. They are noted. If you need it, I can share my testing spreadsheet.
function onEdit(event) {
// assumes source data in sheet named Samples
// target sheet of move to named Archive
// test column with Completed is col 1 or A
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "Samples" && r.getColumn() == 1 && r.getValue() ==
"Complete") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Archive");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);//changed to delete the row
image(row,numColumns)//call function to process Images send row and last column
}}
function image(row,numColumns){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s1=ss.getSheetByName("Images")//get Images sheet
var lc=s1.getLastColumn()
var data=s1.getRange(row, 1, 1,lc)//get row
var targetSheet = ss.getSheetByName("Archive");
var target = targetSheet.getRange(targetSheet.getLastRow() , numColumns+1,1,1);//add image one column after Samples data on same row.
s1.getRange(row, 1, 1, lc).moveTo(target);
s1.deleteRow(row)//delete the copied image row.
}
First post. I have just started to learn to code (2 days in) but this is way over my head for now.
I want to be able to email the contents of a row (all cells in that row) by right clicking the grey cell that highlights the entire row and then choosing an option in the drop down menu that either allows me to enter an email address or sends to a specific address that is entered into the code (I will always be sending to the same address).
In fact, choosing the option in the drop down menu isn't a necessity, just how I envisage it but any solution that allows me to bypass copying the row and pasting it into an email would work.
Any help would be greatly appreciated.
Cheers
Ok, so I know this is probably terrible coding but as I said, until a couple of days ago I had never even read a code, let alone tried to write anything. So please be nice!
So with some serious googling, copy/pasting, editing for my needs etc and using the very little I know, here is what I have come up with:
function onEdit(event){
//transfer a row once specific value entered into a cell within that row (without deleting original row)
// assumes source data in sheet named Needed
// target sheet of move to named Acquired
// test column with yes/no is col 4 or D
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "Sheet1" && r.getColumn() == 5 && r.getValue() == "Email JM") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Sheet2");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).copyTo(target);
}
};
function onEditEmail(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var startRow = 1; // First row of data to process
var numRows = 1; // Number of rows to process
// Fetch the range of cells
var dataRange = sheet.getRange(startRow, 1, numRows, 5)
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var message = row[0] + ", " + row[1] + ", " + row[2] + ", " + row[3]; //whole row
var subject = "New Order";
if (row[0] != ""){
MailApp.sendEmail("example#email.com", subject, message);
//delete row straight after it is sent
sheet.deleteRows(1, 1);
}
}
}
Row needing to be emailed is transferred to a different blank sheet when I enter "Email JM" at the end of the row and by setting data validation for this column this can be done by clicking the drop down arrow (no typing). My onEditEmail trigger is set to every minute, which sends all the columns I need from "Sheet2) and then immediately deletes the row so it is not resent a minute later.
The only problem is if 2 orders are entered inside a minute, but this is a very small chance of happening (although I guess it will sooner or later).
Please point out where I can improve this.
Cheers