So I am trying to run a script in Google Sheets. I have two sheets, one of which is a master list with all of my data and another of which is a sheet I am using to filter results when I want to look through a specific date/category. I'm doing this by having three cells in my filtering sheet that I would fill out information to match up with the master list and then having a button that would copy the data over. Here is my code:
function filter() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName("Total");
var month = ss.getSheetByName("Filter").getRange("A2").getValue();
var payment = ss.getSheetByName("Filter").getRange("B2").getValue();
var category = ss.getSheetByName("Filter").getRange("C2").getValue();
for(var i = 1; i < 200; i++)
{
var p = s.getRange(i, 5);
var c = s.getRange(i, 6);
var m = s.getRange(i, 7);
if( p.getValue() == payment && c.getValue() == category && m.getValue()
== month){
var targetSheet = ss.getSheetByName("Filter");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(i, 1, 1, 7).copyTo(target);
}
}
}
Using && conditional inside of my if statement results in nothing appearing. Using || conditional results in everything appearing. I can't seem to figure out how to make it so that I can sort by just one category OR all categories.
Thanks in advance!
You need all 3 values using a short-circuit comparitor (&& and ||). If the condition isn't met, the script exits. You also need to clean up your logic tests by grouping items in parentheses.
For instance,
if(p.getValue() == payment && m.getValue() == month)
returns values with "September" and "Cash" only. "Category" is irrelevant.
Using
if((p.getValue() == payment && m.getValue() == month) || (p.getValue() == payment && c.getValue() == category))
will return a row if the payment and the value match or if payment and category match, regardless of month.
Breaking conditions out using parentheses will help. Also, reconsider using comparators that short circuit a script. This post describing the difference may help.
Related
Dear Oracles of the script,
I have been trying to get some script to automatically add a row beneath the one I have inputted on, but only if the Balance is anything BUT zero. If it's zero, I don't want another row adding.
I've tried a few scripts and looked around the site, and tried them with triggers with on edit, but they just seem to add a row, despite me trying to state a condition for them to trigger.
function onEdit(event) {
var eventRange = event.range;
if (eventRange.getColumn() == 12) { // 12 = column of input that triggers it
var columnXRange =
SpreadsheetApp.getActiveSheet().getRange(eventRange.getRow(), 13,
eventRange.getNumRows(), 12); /// column number it affects
var values = columnXRange.getValues();
for (var i = 0; i < values.length; i++) {
if (!values[i][0]) { // If cell isn't empty
values[i][0] = '0';
}
}
columnXRange.setValues(values);
}
}
function Clear(e){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('AGL');
sheet.getRange('N4:N').clearContent();
}
function AddRow() {
var sheet = SpreadsheetApp.getActiveSheet();
if (sheet.getName() == "AGL") {
var activeCell = sheet.getActiveCell();
if (activeCell.getColumn() == 13) {
var Balance = sheet.getRange('N:N');
if (Balance != '0'); {
sheet.insertRowAfter(activeCell.getRow());
}
}
}
}
In column N, I have an array formula working out the balance of stock in, against stock out.
In column M, I have a script running that will add a 0 when something is put into column L. I also have a script that erases the output of the array formula, so they don't get tangled up with each other.
I would like, when editing a row, when I place a figure in column L or M, and if the balance in Column N is greater than 0, I would like a new row adding underneath. (If you can get it to add the values from A & B, that'd be a bonus, but not fussy.) If the balance is 0, I don't want a row adding.
Currently, I have mixed results with it just adding a row every time I edit column N.
Requirement:
Add a row when columns L or M are edited and the value in column N is greater than 0 (& if possible, add values from columns A & B to the new row).
Solution:
This is pretty much all you need for your script. I've added comments to explain what each part is doing so it should be pretty easy to follow.
function onEdit(e) {
//define edited sheet, row number and column number
var sh = e.source.getActiveSheet();
var row = e.range.getRow();
var col = e.range.getColumn();
//get value of column N for edited row
var val = sh.getRange(row, 14).getValue();
//if columns L or M are edited and column N is greater than 0
if ((col === 12 || col === 13) === true && val > 0) {
//insert row below edited row
sh.insertRowAfter(row);
//get values of columns A and B of edited row
var vals = sh.getRange(row, 1, 1, 2).getValues();
//set values in columns A and B
sh.getRange(row+1, 1, 1, 2).setValues(vals);
}
}
Notes:
I haven't included any of your other functions, only adding a row, you can incorporate these in this function if you desire.
You won't be able to run this script manually at all (it'll fail on the first line), it'll just run automatically when the sheet is edited.
References:
Event Objects
Class Sheet
I have been working on this for a few days and have turned the internet and these forums upside down, but can't find the answer anywhere. I've learned some scripting along the way, but have determined that what I need to do is way beyond my limits. Any help at all or guidance would be SUPER well received!!
I'm setting up an order sheet system for my and my wife's handmade 'business' (project). Essentially I need to move order 1 (columns a-w) from 'ready to ship" when "done" is entered into column X, onto the next blank row on "sent", discounting the formulas in columns w-z. So when its checking for a blank row, it should only check columns a-w.
This means that once the order info has moved across onto the new sheet, we have extra functionality added in those columns (I hooked it up to send an email apologising, if an order will be late - hope not, but better to be prepared, with our postal service :) )
Sample sheet here.
I originally learned how to write a 'move row if value added in column x - and learned about triggers and how to call the sheets etc.
Then, because column x, y and z have formulas, I either end up deleting them when I move the whole row - not cool. Or I end up (if i move only columns a- w) having the new (moved) row, showing up in row 9 million, because it sees the formulas as not blank rows. Ive tried editing a 'find row based on column' script, that seemed to work okay, but then i tried to put the two together and thats when it blew up.
Can anyone tell me where i'm going wrong here?
function lastRowForColumn(sheet, column){
// Get the last row with data for the whole sheet.
var ss = spreadSheetApp.getActiveSpreadSheet();// this gets you the
active spreadsheet in which you are working
var sheet = ss.getSheetByName('Sent');
var r = event.source.getActiveRange();
var data = sheet.getRange(1, "A", numRows).getValues();
var numRows = sheet.getLastRow();
// Iterate backwards and find first non empty cell
for(var i = data.length - 1 ; i >= 0 ; i--){
if (data[i][0] != null && data[i][0] != ""){
return i + 1;
var ss = spreadSheetApp.getActiveSpreadSheet();// this gets you the
active spreadsheet in which you are working
var sheet = ss.getSheetByName('Sent');
var r = event.source.getActiveRange();
if(s.getName() == "Ready To Send" && r.getColumn() == 25 &&
r.getValue() == "Sent") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Sent");
var lastRow = lastRowForColumn();
var target = targetSheet.getRange(targetSheet, i, 1);
s.getRange(row, 1, 1, 10).moveTo(target);
s.deleteRow(row);
}
}
}
}
Try this:
function onEdit(e){
var rg=e.range;
var sh=rg.getSheet();
var name=sh.getName();
var col=rg.columnStart;
if(name!='Sheet To Send'){return;}
if(col==24 && e.value=='done') {
var ss=e.source;
var sheet=ss.getSheetByName('Sent');
var row=rg.rowStart;
var srg=sh.getRange(row,1,1,23);
var data=srg.getValues();
var drg=sheet.getRange(sheet.getLastRow()+1,1,1,23);
drg.setValues(data);
sh.deleteRow(row);
}
}
I think what you want is to use getDisplayValues. That solves both your (formulas aren't blank problem (make sure your formulas display blank when the row is unused) and also the it isn't moving my numbers problem).
Try:
moveMe = [];
rowArray = s.getRange(row,1,1,10).getDisplayValues();
moveMe.push(rowArray);
targetSheet.appendRow(moveMe);
s.deleteRow(row);
You might have to wrestle the appendRow a bit but it is the cleanest. If it doesn't work as is try skipping the push step and doing
targetSheet.appendRow(rowArray);
BETTER (edit):
You need to change first your data row to:
var data = sheet.getRange(1, "A", numRows).getDisplayValues();
and then later you can simply do:
targetSheet.appendRow(data[i]);
s.deleteRow(row);
This function takes an edit event and checks if the values edited was changed to 'Sent'. It then copies that row into the sent sheet in the same spreadsheet, maintaining the values.
function onEdit(e){
if(e.value != 'Sent') return false; //If not changed to 'Sent' then stop.
var range = e.range; //get the range the edit occured in.
if(range.getColumn() != 25) return false;//see if it was column number 25, ie. Column Y
var sheet = range.getSheet();
if(sheet.getName() != 'Ready To Send') return false;
var editedRow = sheet.getRange(range.getRow(), 1, 1, sheet.getMaxColumns()); //Select the row.
var sent = sheet.getParent().getSheetByName('Sent');
sent.appendRow(editedRow.getValues()[0]); //Copy row to Sent sheet.
sheet.deleteRow(range.getRow()); //Deleted the original row
}
I have put together a google spreadsheet that highlights the first four cells in a row if the value in cell 6 is within 3 days of the current date, but I cannot figure out how to add an if/and statement to exclude all entries that have "East" in row E.
This is the code that I have had success with, minus the exclusion of East not highlighting.
onEdit(e) {
if (e) {
var ss = e.source.getActiveSheet();
var r = e.source.getActiveRange();
if (r.getRow() != 2 && ss.getName() == "Sheet1") {
Days_Remaining = ss.getRange(r.getRow(),6).getValue();
rowRange = ss.getRange(r.getRow(),1,1,4);
if (Days_Remaining < 3.8) {
rowRange.setBackgroundColor("#FF0000");
} else if (Days_Remaining == 'N/A') {
rowRange.setBackgroundColor("#ffffff");
} else if (Days_Remaining > 3.9) {
rowRange.setBackgroundColor("#ffffff");
https://docs.google.com/spreadsheet/ccc?key=0AqtCuK7ex8ZNdGxKLUZpQnZ3UzRCV3VoclVDbFVqQnc#gid=0
If you use the new Google Sheets, you can simplify by using Conditional Formatting
If not, you need to get the values in column 5 as well and add an IF block based on that. For example:
Follow_up = ss.getRange(r.getRow(),5).getValue();
And then, before you check Days_Remaining, you should add
If(Follow_up <> "East") {//rest of the checks here }
I'm a new to Script editor on google spreadsheet and not much of a programmer.
I've been working with google docs for sometime now and it has become an important tool in my daily activity.
What I'm trying to do, is the following:
Seek a whole document (with severall sheet such as "1", "2", "3", and so on, corresponding to the number of days a month can hold) and if the column 7 shows a determined value (in my case, it will be RECEBER), pull all the data in that row and write onto a sheet created for this purpose.
What's happening is that I'm using the the event onEdit to trigger this function. At first sight, it would be ideal, but in my case, I copy a lot of data from other spreadsheets and the paste command does not trigger the onEdit event. Instead, I have to edit the cell manually in order to get that row copied onto the other sheet.
I could run it just once, once tha whole days of the month were filled and there were any changes left to do, but what I really want to do is to make it immediately, as soon as the content is inserted into the spreadsheet.
There's also another problem with my code, it has to be fit and adapted to all the other sheets, as the if clause only performs the full operation if the active sheet equals "1". Anyway, I believe there is a simple solution to this.
Here's the code found on the net that already took me halfway:
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "1" && r.getColumn() == 7 && r.getValue() == "RECEBER") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("money");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).copyTo(target);
}
}
I'll appreciate all the help you can give.
Thanks in advance.
Diogo Sousa
-- Updated 12Oct --
I've changed the logics on my code, and as patt0 suggested, I run the script from the menu created. I've tried adapting the code, but I believe there's some section wrong. The script runs, but isn't writing anything at all on my target sheet.
here's the code:
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [ {name: "RECEBER", functionName: "RECEBER"} ];
ss.addMenu("Scripts", menuEntries);
}
function mustBeCopied(sheetName) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetNumber = parseInt(sheetName);
if (sheetNumber <=31 && sheetNumber >=1)
return true;
return false;
}
function RECEBER() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var r = ss.getActiveRange();
if(mustBeCopied(s.getName()) && r.getColumn() == 7 && r.getValue() == "RECEBER") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("money");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).copyTo(target);
}
}
the function Mustbecopied, from what I believe, only sets the range of sheets (1 to 31) eligible;
the function RECEBER will determine if the value on the column 7 will satisfy the condition (RECEBER), so it can retrieve all the row information and move it to the target sheet.
Maybe the trouble is the active sheet thing.. can I us eit in my own advantage and apply the script to the selected sheet?
Also, if I could have both option (whether to apply it to the selected sheet or to the whole document), that wouuld be great and simplify my daily work a lot!
Your question is really multiple questions in one, and I will only deal with the second part, the first part has quite a number of dimensions (how to update summary sheets effectively), which we can discuss with another question maybe.
In order to determine if the data in a particular sheet needs to be copied, you could create a simple function that returns a boolean if the sheet satisfies the condition that it is a number between 1 and 31.
function mustBeCopied(sheetName) {
var sheetNumber = parseInt(sheetName);
if (sheetNumber <=31 && sheetNumber >=1)
return true;
return false;
}
Bear in mind that a sheet with the name "28.7" would satisfy the condition.
The first line of your onEdit() script would then look like this
if(mustBeCopied(s.getName()) && r.getColumn() == 7 && r.getValue() == "RECEBER")
Let me know if this works for you.
--- Oct 14 ---
As far as I can see, the issue that remains, is that when your function was running onEdit, the range (active range) was the cell that was just edited. So the range would be only one cell.
In order for the scrip to run, you would need to highlight each cell to be copied before selecting the menu, which would be tedious to say the least
Further if you have a defined range to be copied, which is the same on each page, we can copy that range every time, or iterate through each row in the column to look for "RECEBER" and get those rows copied.
Further We can try to update your function so that iterate through all the sheets is a many similar to this.
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i = 0; i < sheets.length; i::) {
var s = sheets[i];
if (mustBeCopied(s.getName()) {
var range = s.getRange(someRow, 7, someNumOfRows);
for (var j = 1; j <= someNumOfRows; j++) {
if (range.getCell(j,1).getValue() == "RECEBER") {
//YOUR COPY CODE
}
}
}
}
Let me know how this works.
I'm still learning GoogleApp scripting. Can anyone guide me in the right direction how to apply the same codes on a Google spreadsheet with multiple sheets with different sheet names? Maybe I need a script for a loop?
Thank you for your help in advance!
This is the script I have so far:
function MakeRowGray() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var columnD = sheet.getRange(2, 2, sheet.getLastRow()-1, 1); // Row B
var dValues = columnD.getValues();
var columnE = sheet.getRange(2, 3, sheet.getLastRow()-1, 1); // Row C
var eValues = columnE.getValues();
for (var i = 2; i < dValues.length + 2; i++) {
if (dValues[i-2][0].toUpperCase() == 'Y' && eValues[i-2][0].toUpperCase() == 'Y') { // Checks for 'Y' in both D and E columns (Participated & Received)
// If they're both yes, make them gray...
sheet.getRange(i, 1, 1, 7).setBackgroundColor("#CCCCCC"); // Make A through H gray
}
else if (dValues[i-2][0].toUpperCase() == 'Y' && eValues[i-2][0].toUpperCase() != 'Y' && eValues[i-2][0].toUpperCase() != 'W' && eValues[i-2][0].toUpperCase() != 'W?') // IN PROGRESS CODE -- MAKE ROW BLUE??
{
sheet.getRange(i, 1, 1, 7).setBackgroundColor("#AAAAFF"); // Make A through H blue
}
else if (dValues[i-2][0].toUpperCase() == 'Y' && eValues[i-2][0].toUpperCase() == 'W?') // Not sure if Waiting or not (W?)
{
sheet.getRange(i, 1, 1, 7).setBackgroundColor("#FFBB00"); // Make A through H slightly orange
}
else if (dValues[i-2][0].toUpperCase() == 'X' && eValues[i-2][0].toUpperCase() == 'X') {
sheet.getRange(i, 1, 1, 7).setBackgroundColor("#FF0000"); // Red
}
else if (dValues[i-2][0].toUpperCase() == 'Y' && eValues[i-2][0].toUpperCase() == 'W') {
sheet.getRange(i, 1, 1, 7).setBackgroundColor("#FFFF00"); // Yellow
}
else
{ // Reset...
sheet.getRange(i, 1, 1, 7).setBackgroundColor("#FFFFFF");
}
}
};
You've identified the need to change a function you've written so it can be applied in a broader way than it currently supports. This type of work is generally referred to as refactoring.
In your case, this could be the thought process to follow...
Since you want to do the same thing on multiple sheets, generalize the current function to operate on an arbitrary Sheet. The definition of the MakeRowGray() function should be changed to accept a sheetName as a parameter. If you still want the existing behavior to be preserved, i.e. calling MakeRowGray() without any parameter will operate on Sheet1, that can be accomodated.
function MakeRowGray(sheetName) {
sheetName = sheetName || 'Sheet1'; // Default to operate on Sheet1
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
...
Make that change, and test it. Does the function still behave as it used to? Can you pass in the name of one of your other sheets, and does it work there?
Next, write a new function that handles the problem of iterating through your various sheets. This function will pass off work to the refactored MakeRowGray().
function makeAllSheetsGray() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
for (var i=0; i<sheets.length; i++) {
MakeRowGray( sheet.getName() );
}
}
Test that this function does what you expect. Does it find all sheets, regardless of name? Do the right names get passed to MakeRowGray()?
Improve things / tidy up.
This is an important step for future maintenance and re-usability.
Do the function names make sense?
For example, MakeRowGray() does not clearly indicate what the function is actually doing, probably because of previous refactoring. A name like conditionallyColorSheetRows would be an improvement. The new function, makeAllSheetsGray(), should adapt as well, since it was based on the previous inappropriate name.
Do variable names make sense?
Are you doing work you don't need to?
For example, in makeAllSheetsGray() we're getting an array of Sheet instances, then passing the name of the sheet to MakeRowGray() where we use the name to get a handle on a Sheet instance. A further refactoring to use just the Sheet instances would save some processing. There may be reasons to leave things as they are, but since Google Apps Scripts have limited execution time, it's always smart to look for ways to reduce cycles.