Creating a loop that modifies row after row in google Scripts - loops

rookie coder here. I'm working in Google Scripts for a project and here's what I'm trying to do.
Essentially, I am trying to access a specific spreadsheet (called "Top Sheet") through an AICP address that has been entered in cell K2 of my "2013" spreadsheet. The Information that I am accessing in the "Top Sheet" spreadsheet is a single number located in cell K46. Then, I am taking the value of cell K46 (located in "Top Sheet") and placing it into a new cell (N2) in the "2013" spreadsheet.
Here's what that looks like (and it works).
function GetJobActuals() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s2 =SpreadsheetApp.openById(ss.getSheetByName("2013").getRange("K2:K2").getValue());
var v2 = s2.getSheetByName("Top Sheet").getRange("K46:K46").getValue();
ss.getSheetByName("2013").getRange("N2:N2").setValue(v2);
var s3 = SpreadsheetApp.openById(ss.getSheetByName("2013").getRange("K3:K3").getValue());
var v3 = s3.getSheetByName("Top Sheet").getRange("K46:K46").getValue();
ss.getSheetByName("2013").getRange("N3:N3").setValue(v3);
What I'm trying to do now, it make this happen for all of the preceding cells in the "2013" spreadsheet. For example, this code works for one number. Cell K2 in "2013" tells it to retrieve the value of cell K46 in "Top Sheet" and then returns with that value and places it into a new cell, N2.
How would I loop this so that this process would happen for the cell range of K2 - K60 and the repopulate the new cell range of N2 - N60?
Man I hope this makes sense, any and all help is appreciated. And again, the above code works, just trying to make a loop that can handle more than one thing at a time.

The answer should become clear to you if you use Sheet.getRange(row,column) instead of Sheet.getRange(a1Notation). By using row and column numbers instead of A1 notation, you can apply a for loop to acheive your goal.
ss.getSheetByName("2013").getRange("N3:N3").setValue(v3);
becomes
ss.getSheetByName("2013").getRange(3,14).setValue(v3);
^^^^
Row = 3, Column = 14 ("N")
So you can then iterate over rows:
for ( row=2; row<=60; row++) {
...
var s3 = SpreadsheetApp.openById(ss.getSheetByName("2013").getRange(row,11).getValue());
...
ss.getSheetByName("2013").getRange(row,14).setValue(v3);
...
}
Details left to you, but that's the basic structure for your loop. There are more efficient ways to make this work, but since you're new to this, worry about that after you get the basics working.

Related

Apps Script - compare two arrays, find differences (value and index of cells)

I have two vertical 1D arrays, one with existing data, and another that could (potentially) have differences.
I want to be able to compare the two arrays, find the differences AND the index of each cell that is different.
Then, I would use that information to find the appropriate cells in the existing data that need to be changed, and set the new values in those specific cells, without having to copy and paste the entire array.
For example:
Existing Data
New Data
John Smith
John Smith
012345
012345
6th grade
7th grade
555-1234
555-1357
Trumpet
Trumpet
5th period
2nd period
Jane Smith
Jane Smith
js#email.com
js#email.com
In this case, the code would see that rows 3,4, and 6 have differences, save those new values and their places in the array, then update the appropriate values in the main data list without changing anything else.
I've tried multiple ways to compare the two arrays AND get the index of the rows that have differences and this is as far as I've gotten without an error or a 'null' result:
function updateInfo() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var currentSheet = ss.getSheetByName("CURRENT");
var infoSheet = ss.getSheetByName("INFO Search");
var origVal = infoSheet.getRange(5,2,52,1).getValues();
var newVal = infoSheet.getRange(5,4,52,1).getValues();
var list = [];
var origData = origVal.map(function(row,index){
return [row[0],index];
});
Logger.log(origData);
var newData = newVal.map(function(row,index){
return [row[0],index];
});
Logger.log(newData);
This just gives me the value and index of each cell.
Is there a fast and efficient way to compare the two arrays, get the data I need, and change the values in just certain cells of the original column?
I can't just copy and paste the whole column over because there are formulas embedded in various rows that need to remain intact.
Thanks for any help you can provide!
Solution:
Iterate through the new data array. For each value, check whether the new value matches the old one. If that's the case, add the corresponding value and index to your list.
Then iterate through your list, and for each pair of value and index, write the value to the corresponding cell.
Code snippet:
newVal.forEach(function(row, index) {
if (row[0] === origVal[index][0]) {
list.push([row[0], index]);
}
});
var firstRow = 2; // Change according to your preferences
var columnIndex = 1; // Change according to your preferences
list.forEach(data => {
sheet.getRange(data[1] + firstRow, columnIndex).setValue(data[0]);
});
Notes:
I don't know where should the data be written, so I'm using undefined sheet, and also random values for firstRow and columnIndex. Please change these according to your preferences.
It would be much more efficient, from a script perspective, to write the whole column to your destination range at once, since that would minimize the number of interactions between the script and the spreadsheet (see Use batch operations). Since you want to write only the updated data, though, I provided a script that does this.

identify/merge adjoining cells within an [] range array in order to limit the #ranges within the [] range array

Here is a google sheet script that
Protects the sheet, then
Creates a range of all "formula" cells in the worksheet, then
Creates a [] of all worksheet cells that are NOT formulas (aka not in the formulas range), then
Unprotects all cells in []
function protectFormulas() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
let up = [];
let protection=sh.protect();
const range = sh.getDataRange();
const formulas = range.getFormulas();
formulas.forEach((r, i) => {
r.forEach((c, j) => {
if (c == '') {//if not a formula then add range to up
up.push(sh.getRange(i+1,j+1));
}
});
});
protection.setUnprotectedRanges(up);//unprotect ranges in up
}
However
Since "up" only has "individual cell" ranges, I would like merge all adjacent cells together within "up" in order to limit the #ranges within it.
Is there any way to do that?
Why do I want to do this?
It's a very large sheet with a lot of formulas and blank space
Limit the operations of the script, if possible , since it takes a loong time to run.
Having individual cell ranges slows down the opening/closing of the protected page function
because the protected sheet is a template that is going to be duplicated several times. I wrote another function that duplicates the sheet with the protections intact which is obviously running really slow because of the above.
the sheets have to be protected is because there are other users that are going to be interacting with the sheets. They will be entering information into some of the non formula cells (which is of course why sensitive parts of the sheet need to be protected)

Google Sheets Search from Database and insert into specific cell on edit

i need some help from someone who is more experience than me.
I've the following formula
=WENNFEHLER(SVERWEIS($B$3;$B6:C;{2};0))
and the following script
function copyIntoCell() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('C3').activate();
spreadsheet.getRange('A3').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
};
How is it possible to add the formula into the script and also make it "onEdit" when a name entered into B3 it should auto insert the number into C3 from B6:C when B3 match with the database.
Also is it possible to autocomplete when I enter a word into B3 it suggest me the names from the database with the word I tipped in? This one is not important but would be nice.
Here is the example
Thanks for any help and idea I can get to complete what I looking for.
Combine script and formula as following:
Check if the edit was performed in column B
Retrieve the active row
setFormula() to assign your formula to the active row in column C
If the entered name is not found in the database and the formula returns the error "#NAME?" - delete the formula again
function onEdit(e) {
//check if edit takes place in the second column (B)
if(e.range.getA1Notation() =="B3"){
//proceed
var spreadsheet = SpreadsheetApp.getActive();
var row = e.range.getRow();
var formula = "=IFERROR(VLOOKUP($B$3,$B6:C,{2},0))";
//set formula to active row in column C
var cell = spreadsheet.getActiveSheet().getRange(row, 3);
cell.setFormula(formula);
}
};

VBA to paste only certain values of cell from one sheet to another

Can some one help me with the below code, what I am looking for is, from sheet "Form" certain values of cells mentioned in 2 sets of Array.
1st set of Array should get copied to sheet "Tracker" C3 onward and second set of array from next cell after the 1set of array ends say EF3 onwards.
whereas now first sett is its pasting from A3 and second from A4. Please let me know in case of any question.
Following is the code which I am using now:
Sub AddEntry()
Dim LR As Long, i As Long, cls
Dim LR2 As Long, j As Long, cls2
cls = Array("C2", "C3", "G2", "G3", "C5", "C6", "C7", "C8", "C9", "C10", "C11", "C12", "C13", "A17", "C17", "D17", "F17", "G17", "H17", "A18", "C18", "D18", "F18", "G18", "H18", "A19", "C19", "D19", "F19", "G19", "H19", "A20", "C20", "D20", "F20", "G20", "H20", "A21", "C21", "D21", "F21", "G21", "H21", "A25", "B25", "C25", "D25", "E25", "F25", "G25", "H25", "A26", "B26", "C26", "D26", "E26", "F26", "G26", "H26", "A27", "B27", "C27", "D27", "E27", "F27", "G27", "H27", "A28", "B28", "C28", "D28", "E28", "F28", "G28", "H28", "A32", "C32", "E32", "G32", "H32", "A33", "C33", "E33", "G33", "H33", "A34", "C34", "E34", "G34", "H34", "A35", "C35", "E35", "G35", "H35", "A39", "D39", "F39", "A40", "D40", "F40", "A41", "D41", "F41", "A45", "C45", "E45", "G45", "A46", "C46", "E46", "G46", "A47", "C47", "E47", "G47", "D51", "D52", "D53", "D54", "D55", "D56", "D57", "D58", "D59", "D60", "D61", "D62", "D63", "D64", "D65", "D66", "D67")
With Sheets("Tracker")
LR = WorksheetFunction.Max(3, .Range("C" & Rows.Count).End(xlUp).Row + 1)
For i = LBound(cls) To UBound(cls)
.Cells(LR, i + 1).Value = Sheets("Form").Range(cls(i)).Value
Next i
End With
cls2 = Array("E51", "E52", "E53", "E54", "E55", "E56", "E57", "E58", "G59", "E60", "E61", "E62", "G63", "E64", "E65", "E66", "E67", "C70", "D70", "E70", "F70", "G70", "H70", "C71", "E71", "G71", "C72", "E72", "G72", "C73", "E73", "G73", "C74", "E74", "G74", "C75", "E75", "G75", "C76", "E76", "G76", "C77", "E77", "G77", "C78", "E78", "G78", "C79", "E79", "G79", "C82", "D82", "E82", "F82", "G82", "H82", "C83", "E83", "G83", "C84", "E84", "G84", "B88", "B89", "B90", "B91", "C88", "C89", "C90", "C91", "D88", "D89", "D90", "D91", "E88", "E89", "E90", "E91", "F88", "F89", "F90", "F91", "G88", "G89", "G90", "G91", "H88", "H89", "H90", "H91")
With Sheets("Tracker")
LR2 = WorksheetFunction.Max(3, .Range("EW" & Rows.Count).End(xlUp).Row + 1)
For j = LBound(cls2) To UBound(cls2)
.Cells(LR, j + 1).Value = Sheets("Form").Range(cls2(j)).Value
Next j
End With
End Sub
Assuming that you want to start cell entries in sheet "Tracker" more to the right, you can add the column number instead of +1 (= column A) and write as follows:
Array 1: assigning cell values starting from column C
.Cells(LR, i + [C1].Column).Value = Sheets("Form").Range(cls(i)).Value
Array 2: assigning cell values starting from column EF
' should be LR2 instead of LR :-)
.Cells(LR2, j + [EF1].Column).Value = Sheets("Form").Range(cls2(j)).Value
Note
[C1].column returns the column number (in any worksheet), e.g. column C Counts 3.
I took a look at your file; the first thing I did was flip through the VBA & try to compile it -- which incidentally, I would recommended to anyone as a first step with a downloaded XLSM. (I haven't seen a malicious macro yet and I'd like to keep it that way!)
I can see that this file has been a "work in progress" because there are bits of code here and there that don't compile properly, such as Me statements pointing to a missing userform, and references to mis-named worksheets such as Form (View) instead of View_Form.
Ideally, this project should be moved from Excel to Access. Excel can be used for filling forms and storing data, but if this is potentially going to sizable, you're best off to use "the right tool for the job". Duplicating your form(s) into Access forms instantly removes the need to copy certain cells to certain sheets, not to mention ease of validation, reporting, security, and unlimited room for expansion plus ease of moving data between Excel, Access, Outlook, etc.
(You even called the spreadsheet a database in one spot!) If your concern is that you're unfamiliar with Access, if you designed this workbook, migration to Access will be a breeze once you figure out the basics of table and form design.
Even Outlook has some pretty nifty form capabilities which can autopopulate the data table when an emailed form is received.
If you need to stay in Excel, how about a User Form instead of the sheet-based form? I too often see people forgetting about Office's built-in features and starting from scratch. That being said, I've been a user of MS Office for 25 years and have never used an Excel User Form. When I think "form", I think MS Access.
Another option, if you want to stay with the worksheet-based form, instead of listing all the cells in the array etc, a minor redesign could make it simpler. One way would be to have a hidden row on the form tab so you have a single uninterrupted line of all the data you need to store. For example, you could hide row 1 and 2, make row 1 the headings like Sourced Processed Year Address etc. and then row 2 could be an "interim" place to store the data, so A2 formula is =C2, B2 is =C3', B3 is=C5` etc.
Finally another sneaky option could be to add hidden comments in each cell that has data that needs to be saved, and then when the form is complete, loop through all the cells looking for comments, and each comment would contain a title or cell reference indicating where that cell's data needs to go.
The destination should be a very straightforward table Use as many columns as you need, but it's not a place for formatting or formulas. (Think database!)
For example, C2 (Sourced By) could have a hidden comment like "Tracker:C" then when the form is filled, you could parse the comments and move the data dynamically (instead of hardcoding 250 cell addresses!) with something like:
Option Explicit
Sub moveData() 'untested; example only
Dim cell As Variant, nextBlankRow As Integer
Dim comm As String, sht As String, col As String
nextBlankRow = 5 'calculate this somehow
'loop through cells with comments
For Each cell In ActiveSheet.Cells.SpecialCells(xlCellTypeComments)
If cell.Comment.Text <> "" Then
'get comment
comm = cell.Comment.Text
'extract location for data like "Sheetname:Columnletter"
sht = Left(comm, InStr(comm, ":") - 1)
col = Right(comm, Len(comm) - InStr(comm, ":"))
'populate correct location with data
Sheets(sht).Range(col & nextBlankRow).Value = cell.Value
End If
Next cell
End Sub
As with anything in Excel (or Office in General) there are a dozen ways you could accomplish the same task. Opt for the ones that don't involve repeating the same code over and over, nor hardcoded data. Planning for future (unexpected) growth is very important, as is debugging as-you-go, which is my last suggestion:
Option Explicit
at the top of every module, and Alt+DLcompile often, removing or commenting-out unused code.
Bottom line, best bet: Access, Excel, Outlook all have form capabilities built in. use a form for a form and you'll save yourself a headache now and later.
Hopefully this gives you some ideas.
Good Luck!

Array elements disappearing / not loading

I have no idea what is going on here and it's a little bizzare.
I'm adapting a VBA macro into a VB.net project, and I'm experiencing what I would describe as some extreemly unusual behavior of a method I'm using to pass data around in VB.net. Here's the set up...
I have, for indexing reasons, a collection that consists of all open orders:
Public allOpenOrders As New Collection
Within this collection, I store other collections, indexed by account number, that each contain information about each open order in an array that is three elements long. Here is how I'm populating it:
openOrderData(0) = some information
openOrderData(1) = some information
openOrderData(2) = some information
SyncLock allOpenOrders
If allOpenOrders.Contains(accountNumber) Then
'Already in the collection...
accountOpenOrders = allOpenOrders(accountNumber)
accountOpenOrders.Add(openOrderData)
Else
'Not already in collection
accountOpenOrders = New Collection
accountOpenOrders.Add(openOrderData)
allOpenOrders.Add(accountOpenOrders, AccountNumber)
End If
End SyncLock
Here's the thing, if I place a stop after end synclock and check the collection, I can clearly see that the array with all data is there, plain as day. However, when I move on in my code (this is occuring in another thread after the preceeding code has executed) to retrieve it and write it to a workbook...
If allOpenOrders.Contains(accountNumber) Then
accountOpenOrders = allOpenOrders(accountNumber)
For each openOrderArray In accountOpenOrders
OutputSheet.Cells(1, 1).value = accountNumber
For counter = 0 to 2
OutputSheet.Cells(1, counter + 2).value = openOrderArray(counter)
Next counter
Next openOrderArray
End If
I get the first element of the array in column B, but C and D are blank. Even more puzzling, if I put a stop right after the allOpenOrders.Contains line I can look at the collection and the last two elements of the array are now blank. Most puzzling of all, they aren't just blank, they are blanks, a number of blanks equal in length to the original field I recorded in that element of the array?!
Any ideas are appreciated. I can tell you I'm using the same type of method to load other data in this workbook with no problems. These are also the only instances in which the allOpenOrders collection is touched... I'm so confused by these results.

Resources