Why does my Apps Script reference the wrong row? - google-apps-script

The Goal
I am trying to create a spreadsheet using some custom functions. The purpose for the sheet is to keep score in a quizzing competition. The top row has the question number, the second row the score, and the third number the number of fouls for that question.
The Problem
After noticing some problems with the score calculation, I was able to narrow the problem down to the part of the code where I add up the fouls that occurred prior to the current question. It seems that no matter what I do, the code sums over the question row, not the foul row.
Attempted Solutions
The extremely strange thing is that no matter what I change the reference cells to, it always produces the same result (i.e. it still references the question row same as it always has).
Example
I'm not sure if that makes any sense, but I've made an example sheet here so you can see what I'm talking about and try to figure out what is going on. Keep in mind that I'm well aware that I could accomplish what I'm trying to do in the example sheet with a simple built-in formula, but since there's no way to use worksheet formulas on the Apps Script side, I have to create my own version of it. I've made the example editable for anyone with the link, so you should have full access to it; but if you have problems, just let me know, and I'll see what I can do to fix it.

In your For loop, you are summing the indexes rather than the values:
Try:
for (var PrevValue in PrevValues[0]) {
Sum = Sum + Number(PrevValues[0][PrevValue]);
}
EDIT:
You'll also need to account for the case where you pass in a single cell rather than a range (=mySum($B4:B4)), because in that case the value is passed directly instead of an array.
if(PrevValues instanceof Array){
for (var PrevValue in PrevValues[0]) {
Sum = Sum + Number(PrevValues[0][PrevValue]);
}
}else
Sum = PrevValues;

Related

Adding a Range to existing data in the row below - Apps Script

I'm very new to coding and specifically to JavaScript.
Im trying to use Apps Script to code my google sheet to automate a financial spreadhseet i am making, here is my problem:
I want to input data in a single row, and have it update the row below, for example i want the range B7:M7 to be my input, so lets say for the sake of simplicity all 12 cells in that row will have the value 50
I then want it to transfer onto the range B8:M8, but if that range is already on 50, then i want it to minus that and make it 0. If range B8:M8 was on 0, then i want to make it -50. I hope this makes sense. Thank you
As doubleunary pointed out in the comments, your question lack info about the case, but if you use this code you can get the result you need exactly as you asked for.
please mind that here the range is fixed to work on B7:M7 to B8:M8 as you said.
You can change the getRange() numbers to work with the range you need, even with a dynamic range.
function test(){
var value1 = SpreadsheetApp.getActiveSheet().getRange(7,2,1,12).getValues();
var value2 = SpreadsheetApp.getActiveSheet().getRange(8,2,1,12).getValues();
for(var i=0;i<12;i++)
{
value2[0][i] = value2[0][i]-value1[0][i];
}
SpreadsheetApp.getActiveSheet().getRange(8,2,1,12).setValues(value2);
}
Please, update your question as the community guidelines need.

Sorting Columns Based on Values from different sheet

I've created a table with Names, Type of Sale, Location and Amount. The names is taken from a different sheet with formula ={"";Unique(rawData!A:A)} and for other columns like location Amount and type of sales, I've used Vlookup and Sumifs function based on the requirement.
This is working fine, but, when I try to sort with the googlescript with the code
function autoSort() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const ws = ss.getSheetByName("Summary")
const range = ws.getRange("A2:D1000")
range.sort({column:4, ascending: false})
}
the Amount column is getting sorted but again it is changing back in a blink.
Is this due to the formula ={"";Unique(rawData!A:A)} or I'm doing something else wrong here.. Kindly help
What's going wrong?
When you are "manually" sorting your data with the script, your ={"";Unique(rawData!A:A)} formula does not change. All you are doing is rearranging the other vlookup and sumif physical formulas around the sheet. Since the names never change, neither are your resulting values.
This is a very basic and rudimentary explanation. I'm sure there is a much better explanation for what is happening, but my basic understanding is just that you can't sort formulas because ultimately nothing about the order actually changes
A solution?
Do do everything in one formula in order to group everything together.
I have come up with one to do this.
=ifna(
sort(
{
Unique(rawData!A2:A),
arrayformula(VLOOKUP(Unique(rawData!A2:A), rawData!A2:D10, 2, 0)),
arrayformula(VLOOKUP(Unique(rawData!A2:A), rawData!A2:D10, 3, 0)),
arrayformula(VLOOKUP(Unique(rawData!A2:A), rawData!A2:D10, 4, 0))
},
4, 0
)
)
This pulls all the data at once (including the names) and sorts it as a whole. The VLOOKUP references the unique array, instead of an actual cell reference. This allows the values to be sorted, without rearranging the physical formulas. Below is a link to the spreadsheet I created to test this. This may need to be changed and edited to your liking (especially given that I didn't incorporate any SUMIF formulas), but it does what you appear to be asking for.
https://docs.google.com/spreadsheets/d/1Yj0IhZsbbXvhHQfkhn3P8Ac-A8nq5D3fiOZAsVYiPVs/edit?usp=sharing
This formula creates a blank first row. This can be fixed by filtering out blank values in the unique formula, but I left it as it was right now to avoid confusion. If you would like help incorporating that, just let me know and I can edit the formula.

Replace INDIRECT() in data validation rule

I had a working Excel spreadsheet that used indirect() in the data validation and it worked fine. I uploaded it to sheets and converted it, now the indirect does not work.
I have found a link on the support forum that explains it does not work in Chrome but appears to work in Firefox, and the answers and workarounds seem to be for generating a secondary list... which is what I want, but in a data validation across a row.
I have knocked up a simple test sheet, hopefully public and the script editor is visible:
https://docs.google.com/spreadsheets/d/1KUgrdXKIKlk1DWvDOX9cY3B2VnRH_5h_vKuZJlqUlN8/edit?usp=sharing
Hopefully you can see what I'm after. I want the validation in C8 to be the list of items in the category based in B8; C9 based on B9 etc.
EDIT and Update
The question is about a replacement to indirect() in a data validation rule. While I did find a way round this by using indirect(), I preferred the version mentioned by Desire (to whom I have attributed the answer), but I thought I'd document my solution in case the sheet above becomes unavailable, or you cannot access it, or you just wanted a bit more detail.
So, for My Demo I have this:
In A1:C5 are my lists of data with the titles.
In the range B8:B12 I applied a data validation rule of value in range of A1:C1 - this gives the first dropdown.
In Cell E8 I put the formula =transpose(filter($A$2:$C$5, $A$1:$C$1 = B8)) and then copied this down to E12
Finally I put the following in a function and ran it in the script editor.
function runMeOnce() {
var dst = SpreadsheetApp.getActive().getSheetByName('Sheet1').getRange('C8:C12');
var rules = [];
for (var i = 8; i < 13; i++) {
var src = SpreadsheetApp.getActive().getSheetByName('Sheet1').getRange("E" + i + ":H" + i);
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(src).build();
rules.push(rule);
}
dst.setDataValidations(rules);
}
That's all there is, no more onEdit() triggering.
NOTE There is one downside I bumped into with this method though. I have this in place for 6000+ rows in my actual spreadsheet, and across multiple sheets, with some dropdowns having 50-100 items in. This solution seriously eats into the (current) 2 million cell limit.
Hope this helps someone.
Data Validation rule of the type "List of items" takes only a comma-separated list of values as its parameter, and does not evaluate any formulas you try to put there. It does not matter what the function returns, because it will not be called. If you put, say "=sqrt(A10)" in the field "List of items", that only means that the validation rule will require the string "=sqrt(A10)" to be entered in the cell.
Similarly with "List from a Range". Either what you enter parses as range notation, or it does not. The string "=getValidationRange(B8)" does not parse as range notation, hence the error. The function is never called.
The only type of validation that calls a function is "Custom formula". If you use it, then the validation can be performed as intended: for example,
=match(C8, filter(A2:C5, A1:C1 = B8), 0)
requires the content of C8 to be in the column of the table A2:C5 under the heading that matches the category in B8. However, with a custom formula you do not get a dropdown in a cell.
To get a dynamic dropdown, one can either
Use an auxiliary range
For example, enter filter(A2:C5, A1:C1 = B8) in cell F1, so that the F column is for the categories currently selected. The data validation would be "List from a Range", F1:F. This is a fine workaround for one validation rule, but takes more work when you have multiple ones.
Use a triggered script
Use a script that is triggered on edit and sets data validation rules accordingly; this is discussed in How do you do dynamic / dependent drop downs in Google Sheets? among other places.
Based on the sacrificing a goat issue, I did find a simple(ish) way around the problem that still uses indirect().
Set up the named ranges as previously using the titles in CamelCase. In my example I have CatA, CatB, and CatC - i.e. the white space needs removing.
At the end of a row (or in another sheet) transpose the chosen named range (in cell E8: =transpose(indirect(substitute(B8, " ", ""))) copy this down as far as you need.
At this point it's good to note that because we are unsing builtin functions, the speed is so much better, as can be seen by my example.
Now the painful bit. For each subcategory cell (C8, C9 etc in my example), you need to add the validation independently as a range of E8:ZZ8 (obviously ZZ8 needs reigning in a bit) and E9:ZZ9 etc. It doesn't seem to do referential so if you select all the cell in the column, they all only look at the data you specifically type in the box... I might just not have worked out R1C1 notation here, however. I tried.
This can be scripted on GAS to create the R1C1 validation function and then apply it to the range.

How to change background color of a cell if it is edited after a specific duration?

I have a google spreadsheet which we use to feed inventory purchase and issue data. The only problem is my staff can manipulate purchased quantity, prices and other variables at a later date. I want that if they enter the data in a cell and try to edit it whenever after 12 hours of entering the data, the cell should get highlighted. If possible, the cell should not highlight if I edit the data.
A simple solution is to have a column when this data is entered (if the data is added with a google form, then you are already set to go on that front). At that point all you need is a onEdit triggered function that fetches that timestamp, does var curTime = new Date() and then check what is the difference between them, and if it's greater than 12 hours you do a e.range.setBackground('red') or whatever color you wish (remember that the function must have e defined like function checker(e)).
Having it ignore edits done by you is also simple, just have an a simple
var editUser = Session.getEffectiveUser().getEmail()
if (editUser == 'myEmail#gmail.com`)
return 1;
and it will stop the script if the session users email matches your own.
For future reference please also provide what code you have so far, as currently we don't know what research you have done so far, what worked and what did not. Remember — here you won't get someone to write the program for you, only solve issues you have with code you already have.

How do I keep values in 2 different sheets in sync?

I have a sheet that has some dates on it, like "25/02/2016", listed down a column. On the cells to the right of each date, there are some numeric values.
I need to copy these numeric values to a specific range on a different sheet. Ideally, it would work like a one-way sync, where I would write values on sheet1 and sheet2 would automatically be updated.
I've been looking through the Google Apps Script documentation, but I have no idea where to start. I do have some pseudo-code, just don't how to use it here.
function getDates() {
for (count = 0; count < sheet1.length; count++) {
if (hasDate) {
return(cell);
}
}
}
var numericValuesRows = sheet1.getDates().getRow();
var numericValuesRange = numericValuesRows.selectColumns(C-F);
Just making up method names and syntax. This bit is supposed to find out which rows have dates in them, and then select columns C to F in those rows. The next one is supposed to select the destination as all the cells from row3:columnC to row10:columnF, and copy the previously selected values to there.
var outputRange = sheet2.cellRange(C3-F10);
numericValuesRange.copyTo(outputRange);
I realise it's really crappy pseudo-code, but I'm hoping it at least helps in some way get across what I want to do. What would be the best way to do this?
Use the onEdit() trigger which will trigger when you edit a sheet. You can check the source of the edit to make sure its an edit to the cells you want in a particular sheet. Once that's done, its a matter of using getValue(), setValue() and getSheetByName(). Start here: simple triggers