Looping through a Google App script and setting multiple nonconsecutive values - javascript

This was difficult to title without examples and context. Here goes...
I have a Google app script which searches through a column of student ids (column A on the compiledDATA sheet) and then sets a value (an award) in column B of the same row. This works fine for a single student id, but I need the script to loop and set the same award value for all of the students in the GroupAwardIDs range which is located on a separate sheet called Group Awards.
Here's a link to my sample spreadsheet.
The values to be set are nonconsecutive, and in actual use there may be over a thousand to be set at a time.
How can I achieve this in a quick and efficient way without running into quota issues?
Here's the script (please excuse all the comments - it helps me keep track):
function AwardGroup() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var StarLog = sheet.getSheetByName("compiledDATA");
var GroupAward = sheet.getRangeByName("GroupAward").getValue();
var GroupAwardIDs = sheet.getRangeByName("GroupAwardIDs").getValue(); // THESE ARE THE IDS OF STUDENTS WHO WILL RECEIVE THE AWARD. HOW DO SET VALUES FOR ALL AND ONLY THESE IDS?
var IDs = sheet.getRangeByName("StudentIDs").getValues(); // all of the student IDs
for (var i = 0; i < IDs.length; i++) {
if (IDs[i] == "123461") { // THIS WORKS IF HARDCODE A SINGLE ID
var rowNumber = i+3; // find row and add 3 to compensate for GroupAward range staring at row 3
var StarLogCurrent = StarLog.getRange("B"+rowNumber).getValue(); // locates students award log cell using A1 notation
var appendAward = GroupAward.concat(StarLogCurrent); // prepends new award to previous awards
StarLog.getRange("B"+rowNumber).setValue(appendAward); //write new star log
}
}
}

You want to put GroupAward ("'Group Awards'!B3") to the column "B" of "compiledDATA" with the same row, when StudentIDs ("compiledDATA!A3:A1000") and GroupAwardIDs ("'Group Awards'!B7:B1002") are the same. If my understanding is correct, how about this modification? I think that there are several solutions for your situation. So please think of this as one of them.
Modification points :
Retrieve all GroupAwardIDs.
Remove empty elements in GroupAwardIDs.
Search IDs using GroupAwardIDs and put GroupAward when the IDs is the same with GroupAwardIDs.
Put the values with GroupAward.
Modified script :
Please modify as follows.
From :
var GroupAwardIDs = sheet.getRangeByName("GroupAwardIDs").getValue(); // THESE ARE THE IDS OF STUDENTS WHO WILL RECEIVE THE AWARD. HOW DO SET VALUES FOR ALL AND ONLY THESE IDS?
var IDs = sheet.getRangeByName("StudentIDs").getValues(); // all of the student IDs
for (var i = 0; i < IDs.length; i++) {
if (IDs[i] == "123461") { // THIS WORKS IF HARDCODE A SINGLE ID
var rowNumber = i+3; // find row and add 3 to compensate for GroupAward range staring at row 3
var StarLogCurrent = StarLog.getRange("B"+rowNumber).getValue(); // locates students award log cell using A1 notation
var appendAward = GroupAward.concat(StarLogCurrent); // prepends new award to previous awards
StarLog.getRange("B"+rowNumber).setValue(appendAward); //write new star log
}
}
To :
var GroupAwardIDs = sheet.getRangeByName("GroupAwardIDs").getValues(); // Modified
var IDs = sheet.getRangeByName("StudentIDs").getValues();
// I modified below script.
GroupAwardIDs = GroupAwardIDs.filter(String);
var res = IDs.map(function(e){
return GroupAwardIDs.filter(function(f){
return f[0] == e[0]
}).length > 0 ? [GroupAward] : [""];
});
sheet.getRange("compiledDATA!B3:B1000").setValues(res);
If I misunderstand your question, please tell me. I would like to modify it.
Edit :
You want to add GroupAward to the original values at column B. I understood what you want to do like this. If my understanding is correct, please modify to as follows. In this sample, I used ", " as the delimiter.
var GroupAwardIDs = sheet.getRangeByName("GroupAwardIDs").getValues(); // Modified
var IDs = sheet.getRangeByName("StudentIDs").getValues();
// I modified below script.
var columnB = sheet.getRange("compiledDATA!B3:B1000");
var valColB = columnB.getValues();
GroupAwardIDs = GroupAwardIDs.filter(String);
var res = IDs.map(function(e, i){
return GroupAwardIDs.filter(function(f){
return f[0] == e[0]
}).length > 0 ? [valColB[i][0] ? GroupAward + ", " + valColB[i][0] : GroupAward] : [valColB[i][0]]; // Modified
});
columnB.setValues(res);

Related

In app scripts, confirm that 2 adjacent cells match, then use a directory tab to push data to a specified sheet ID

Beware, I may be overthinking this.
I keep getting into cyclic thought loops when I'm trying to figure out what to do in this situation, So I will try to explain my thinking and where I am at.
Form is filled out on Google Sheets
Form replies are added to the main Form sheet in the "form responses tab"
Code actives, checking to see if the form was filled correctly (columns A and B match)
if they match, it finds the respective google spreadsheet ID that that row needs to go to, by looking at the directory tab.
That item is then sent over to the appropriate list, which is in it's own sheet
This continues for the rest of the rows of the Main QA Forms Responses tab, until all rows have been checked and there are no more entries.
I've been trying to understand this for hours on end, but might be approaching this all from the wrong angle.
As of right now, this is how far i've gotten in the code:
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var r = s.getActiveRange();
var columnSearchNum = 3;
var columnDatastarts = "C";
var formSheetName = "QA Form Responses";
var directorySheetName = "Program Directory";
var matchingProgramSheetIDColumn = 1;
if(s.getName() == formSheetName && r.getColumn(0) == r.getColumn(1)) {
var sourceRow = r.getRow();
var matchingProgram = sourceRow.getRange(0,0).getValue();
var matchingProgramSheetID = s.getName(Programdirectory)....//[code needed1]
//^^^^ I need a line here to pull matching the data inSheetID column where Matching program's string is
//from this code line, Go to that 'program's sheet'
var programSheet = ss.getSheetByID(matchingProgramSheetID);
var programSheetNumRows = programSheet.getLastRow();
//console.log(programSheetNumRows);
var formSheetNumColumns = s.getLastColumn();
var targetRange = programSheet.getActiveRange()
var targetValue = +s.getRange(columnDatastarts+sourceRow).getValue()
//console.log(targetValue);
var programSheetRange = programSheet.getRange(1,columnSearchNum,programSheetNumRows,1);
//console.log(programSheetRange.getNumRows() +" " +programSheetRange.getNumColumns() + " " + programSheetRange.getValues());
var targetRow = findIndex(programSheetRange.getValues(), targetValue);
//console.log(targetRow);
var target = programSheet.getRange(targetRow, 1);
s.getRange(sourceRow, 2, 1, formSheetNumColumns).moveTo(target);
;
}
}
function findIndex(array, search){
//console.log(array);
if(search == "") return false;
for (var i=0; i<array.length; i++){
//console.log("comparing " + +array[i] + " to "+ +search);
if (+array[i] == +search){
return i+1;
}
}
return -1;
}
You want to find a match for a value on the Form Responses sheet with values in a given column on another sheet, and then return the value in the cell adjacent to the matching cell.
There are probably many ways to do this, but the Javascript method indexOf is an obvious choice if using a script. The following is untested, but the logic is sound.
Insert at [code needed1]
// define the Program sheet
var progsheet = ss.getSheetByName(directorySheetName)
// define the first row of data on the program sheet
var firstRowofData = 3
// get the data for columns one and two of the program sheet.
// starting row=3, starting column=1, number of rows = lastrowminus first row plus 1, number of columns = 2
var progsheetdata = progsheet.getRange(firstRowofData,1,progsheet.getLastRow()-firstRowofData+1,2).getValues()
// get Program manager Column
var ProgManager = progsheetdata.map(function(e){return e[0];})
// search the Program manager Column for the first instance of the matching program value
// indexOf returns index if found, or -1 if not found
var result =ProgManager.indexOf(matchingProgram);
if (result == -1){
// couldn't find a matching program
// do something to quit/error message
return
}
else
{
// the id will be in the row returned by IndexOf and in the adjacent column to the right.
var id = progsheetdata[result][1]
}

Return an array of (edited) matched items of two other arrays and (unedited) non-matched items

My goal
I'm trying to link rows in two different sheets in the same spreadsheet according to a value in a cell. The basic idea is that if a value in column B in Sheet1 has a matched value in column B in Sheet2, an hyperlink should be added to the cell with the matched value in Sheet1 linking to the whole row of the matched value in the Sheet2.
What I did
As you can see from the code below, it looks for the matches, if it's found, it edits the matched values to add the hyperlinks. I don't only want to push the "linked" matched values into the new array, I also want the non-matched values without the link. The idea is that the link will be added if a match is found, otherwise the value will still be added, just without the link.
function linkToContacts(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var Sheet1 = ss.getSheetByName("Sheet1");
var Sheet2 = ss.getSheetByName("Sheet2");
var Sheet2ID = Sheet2.getSheetId();
var arrSheet1 = Sheet1.getRange(4,2,Sheet1.getLastRow()-3).getValues(); // -3 because I have 3 row as headers before the data, which start in row 4 col B
var arrSheet2 = Sheet2.getRange(4,2,Sheet2.getLastRow()-3).getValues(); // -3 because I have 3 row as headers before the data, which start in row 4 col B
var arrOutput = [];
for(var i = 0; i<arrSheet1.length;i++){
for(var j = 0; j<arrSheet2.length;j++) {
if(arrSheet1[i][0] === arrSheet2[j][0]){
arrOutput.push(['=HYPERLINK("#gid=' + Sheet2ID + '&range=' + Sheet2.getRange(j+4,2,1,Sheet2.getLastColumn()-1).getA1Notation() + '";"' + arrSheet1[i][0] + '")']);
} else {
arrOutput.push([arrSheet1[i][0]]);
}
}
}
Sheet1.getRange(4,2,Sheet1.getLastRow()).clearContent();
Sheet1.getRange(4,2,arrOutput.length).setValues(arrOutput);
}
The problem
The two arrays contain only unique values respectively. The problem is that, because of the double loop, each item is checked by the length of arrSheet2. So for instance let's imagine this scenario:
var arrSheet1 = [apple,avocado,banana];
var arrSheet2 = [apple,banana,mango,];
the arrOutput will result in:
arrOutput = [
apple(link),apple,apple,
avocado,avocado,avocado,
banana(link),banana,banana
];
In a quick, probably unelegant, way to solve the issue I've tried to delete the duplicates frm arrOutput but obviously the value with the link and the values without are different so the best it can get with this solution is this:
arrOutput = [
apple(link),apple,
avocado,
banana(link),banana
];
Question
Is there a smarter/more efficient way to get to
arrOutput = [apple(link),avocado,banana(link)];
or in case what I'm doing actually makes sense, what I should do to get to the result above?
You could use Array.prototype.filter to create a new array that contains only matching elements. If no elements are found this will return an empty array.
var arrSheet1 = ['apple','avocado','banana'];
var arrSheet2 = ['apple','banana','mango'];
var intersect = arrSheet1.filter(function (element) {
return arrSheet2.includes(element);
});
// ['apple','banana']
Or without an Array.prototype.includes polyfill:
var intersect = arrSheet1.filter(function (element) {
return arrSheet2.indexOf(element) !== -1);
})
Issue:
Double looping and pushing array elements multiple times
Solution:
Use break and conditional if statements to control logic
Flow:
If hyperlink is pushed, break the second sheet loop
Only push Sheet1 element if there's no hyperlinks in Sheet2(i.e., Wait till the last element of Sheet2 is iterated)
Snippet:
for(var i = 0; i<arrSheet1.length;i++){
for(var j = 0, k = arrSheet2.length-1; j<=k; j++) {// modified
if(arrSheet1[i][0] === arrSheet2[j][0]){
arrOutput.push(['=HYPERLINK("#gid=' + Sheet2ID + '&range=' + Sheet2.getRange(j+4,2,1,Sheet2.getLastColumn()-1).getA1Notation() + '";"' + arrSheet1[i][0] + '")']);
break; //break j loop to continue next i loop
} else if(j === k){//push sheet1 only on the last element
arrOutput.push([arrSheet1[i][0]]);
}
}
}
Note:
Use of objects {} might be better. Convert sheet2 array to object {val1:hash,val2:hash,...}. Then you can easily check if sheet1 elements are present in sheet2 using in

Google apps script - using for loop to pull data from range based on condition

I'm having an issue pulling the correct values out of a for loop in Google Sheets.
Here's my code:
Note: this is a snippet from a larger function
function sendEmails() {
var trackOriginSheet = SpreadsheetApp.getActiveSpreadsheet().getName();
var getMirSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Miranda");
//Set a new object to hold conditional data
var holdingData = new Object();
//Create function to get values from origin sheet
var returnedValues = function (trackOriginSheet) {
//Load dynamic variables into an object via returnedValues()
if (trackOriginSheet === getMirSheet) {
var startMirRow = 2; // First row of data to process
var numRowsMir = 506; // Number of rows to process
// Fetch the range of cells A2:Z506
var dataRangeMir = getMirSheet.getRange(startMirRow, 1, numRowsMir, 26);
// Fetch values for each cell in the Range.
var dataMir = dataRangeMir.getValues();
for (var k in dataMir) {
var secondRowMir = dataMir[k];
var intRefDescMir = secondRowMir[3];
var intAdminActionsMir = secondRowMir[4];
//Push returned data to holdingData Object
holdingData.selectedData = secondRowMir;
holdingData.refDesc = intRefDescMir;
holdingData.adminActions = intAdminActionsMir;
}
}
}
Here's a copy of the sheet I'm working on
What I need to have happened here first, is track the origin sheet, then create an object to hold data returned from the returnedValues() function. Later, I'll call the properties of this object into a send email function.
The problem is that I need to be able to pull data from the selected sheet dynamically (the "Miranda" sheet in this case.) In other words, when a user selects the "Yes" option in column I of the Miranda sheet, the first thing this script needs to do is pull the values of the variables at the top of the for loop within the same row that the user selected "Yes." Then, I'm pushing that data to a custom object to be called later.
It's apparent to me, that I'm doing it wrong. There's, at least, something wrong with my loop. What have I done? :)
EDIT:
After reviewing the suggestion by VyTautas, here's my attempt at a working loop:
for (var k = 0; k < dataMir.length; k++) {
var mirColI = dataMir[k][8];
var mirRefDesc = dataMir[k][2];
var mirAdminActions = dataMir[k][3];
var mirDates = dataMir[k][4];
if (mirColI === "Yes") {
var activeRowMir = mirColI.getActiveSelection.getRowIndex();
//Pull selected values from the active row when Yes is selected
var mirRefDescRange = getMirSheet.getRange(activeRowMir, mirRefDesc);
var mirRefDescValues = mirRefDescRange.getValues();
var mirAdminActionsRange = getMirSheet.getRange(activeRowMir, mirAdminActions);
var mirAdminActionsValues = mirAdminActionsRange.getValues();
var mirDatesRange = getMirSheet.getRange(activeRowMir, mirDates);
var mirDatesValues = mirAdminActionsRange.getValues();
var mirHoldingArray = [mirRefDescValues, mirAdminActionsValues, mirDatesValues];
//Push mirHoldingArray values to holdingData
holdingData.refDesc = mirHoldingArray[0];
holdingData.adminActions = mirHoldingArray[1];
holdingData.dates = mirHoldingArray[2];
}
}
Where did all that whitespace go in the actual script editor? :D
You already correctly use .getValues() to pull the entire table into an array. What you need to do now is have a for loop go through dataMir[k][8] and simply fetch the data if dataMir[k][8] === 'Yes'. I also feel that it's not quite necessary to use for (var k in dataMir) as for (var k = 0; k < dataMir.length; k++) is a lot cleaner and you have a for loop that guarantees control (though that's probably more a preference thing).
You can also reduce the number of variables you use by having
holdingData.selectedData = mirData[k]
holdingData.refDesc = mirData[k][2] //I assume you want the 3rd column for this variable, not the 4th
holdingData.adminActions = mirData[k][3] //same as above
remember, that the array starts with 0, so if you mirData[k][0] is column A, mirData[k][1] is column B and so on.
EDIT: what you wrote in your edits seems like doubling down on the code. You already have the data, but you are trying to pull it again and some variables you use should give you an error. I will cut the code from the if, although I don't really see why you need to both get the active sheet and sheet by name. If you know the name will be constant, then just always get the correct sheet by name (or index) thus eliminating the possibility of working with the wrong sheet.
var titleMirRows = 1; // First row of data to process
var numRowsMir = getMirSheet.getLastRow(); // Number of rows to process
// Fetch the range of cells A2:Z506
var dataRangeMir = getMirSheet.getRange(titleMirRows + 1, 1, numRowsMir - titleMirRows, 26); // might need adjusting but now it will only get as many rows as there is data, you can do the same for columns too
// Fetch values for each cell in the Range.
var dataMir = dataRangeMir.getValues();
for (var k = 0; k < dataMir.length; k++) {
if (dataMir[k][7] === 'Yes') { //I assume you meant column i
holdingData.refDesc = dataMir[k] //this will store the entire row
holdingData.adminActions = dataMir[k][3] //this stores column D
holdingData.dates = dataMir[k][4] //stores column E
}
}
Double check if the columns I have added to those variables are what you want. As I understood the object stores the entire row array, the value in column called Administrative Actions and the value in column Dates/Periods if Applicable. If not please adjust accordingly, but as you can see, we minimize the work we do with the sheet itself by simply manipulating the entire data array. Always make as few calls to Google Services as possible.

script to create a list based on conditions

I have a table that has, let's say 2 columns. Column A has names, column B has either empty or non-empty cells.
I want to create a separate list with names from column A where the opposite cell in B is empty.
When I execute the code it doesn't bring any errors, however the list that is created in the output range is only with the name of the first person that has empty cell in front of him, i.e. that name appears 10 times in my output list.
Can anyone help me understand what's wrong with my code?
Thanks in advance!
This is the code that I have.
function HLnames() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("HL test");
var ind = 0;
var fullRange = ss.getRange("A3:B26").getValues();
var rangeList = [];
for (var i = 0; i < fullRange.length; i++) {
if (fullRange[i][1] === "") {
rangeList.push(fullRange[i][0]);
ind = ind + 1;
}
}
var outputRange = ss.getRange(30, 1, ind, 1);
outputRange.setValue(rangeList);
}
It appears you should be using setValues instead of setValue. – Google's Documentation.
Hope that helps :)

Google Script: Format URL in Array.Push

I have a working script that upon form submit, specific rows move from one sheet to another. One of the fields I'm pushing is a url.
On the second sheet, the link is listed and it is hyperlinked, but it's really ugly and I really want to format it so that it shows "Edit" with a hyperlink. I've tried a number of ways, but my knowledge is limited so all I get are errors. I'm hoping someone can point me in the right direction.
Here is my code. I'm very new at this so the script is not at all sophisticated. Any help/suggestions would be appreciated!
function copyAdHoc(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = SpreadsheetApp.setActiveSheet(ss.getSheetByName("Form Responses 1"));
var data = sh.getRange(2, 1, sh.getLastRow() - 1, sh.getLastColumn()).getValues();
// Grab the Headers from master sheet
var headers = sh.getRange(1,1,1,sh.getLastColumn()).getValues();
var date = headers[0].indexOf('Effective Date');
var name = headers[0].indexOf('Employee Name');
var loc = headers[0].indexOf('Location');
var issue = headers[0].indexOf('Description/Question/Issue');
var add = headers[0].indexOf('Additional Information');
var change = headers[0].indexOf('Is this a Qualifying Life Event?');
var url = headers[0].indexOf('Form URL');
var category = headers[0].indexOf('Primary Category');
var status = headers[0].indexOf('Current Status');
var users = headers[0].indexOf('Users');
// Grab only the relevant columns
for(n = 0; n < data.length; ++n ) { // iterate in the array, row by row
if (data[n][change] !== "Yes" & data[n][category] !== "Employee Relations" & data[n][date] !== "") { // if condition is true copy the whole row to target
var arr = [];
arr.push(data[n][url]);
arr.push(data[n][users]);
arr.push(data[n][date]);
arr.push(data[n][loc]);
arr.push(data[n][name]);
arr.push(data[n][category]);
arr.push(data[n][issue] + ". " + data[n][add]);
arr.push(data[n][status]);
var sh2 = SpreadsheetApp.setActiveSheet(ss.getSheetByName("Ad Hoc")); //second sheet of your spreadsheet
sh2.getRange(sh2.getLastRow()+1,2,1,arr.length).setValues([arr]); // paste the selected values in the 2cond sheet in one batch write
}
}
}
It's a bit messy but the only way I know to achieve what you're trying to do would be to insert a column to the left of the hyperlink with the word Edit right justified and then remove the borders between the two.
From your description I am assuming you want the word "Edit" to be Hyperlinked. To do so, try this:
function getHyperlink(url)
{
return "=HYPERLINK(\""+url+"\","+"\"Edit\""+")";
}
function mainFunct()
{
//Do necessary steps
var tarLink = "https://www.google.com";
var tarRng = tarSheet.getRange(rowNum, colNum).setValue(getHyperlink(tarLink));
//perform other steps
}
EDIT:
Forgot to mention, since you're pushing your values to the array... you can do it in a similar way by either just storing the hyperlink in a variable or directly pushing it to the array like all the other values. Or if you're dealing with a hyperlink that has a static and dynamic part, For example: https://stackoverflow.com/questions/post_id, where post_id keeps changing but most of the URL is static, you can easily handle it by just passing the post_id to the getHyperlink function and getting the required Hyperlink in return. Hope this helps.

Categories