Dropdown list with conditions on other dropdown list - validation

How can I make a drop-down list from several lists with a condition from another drop-down list?
Using my image above, let's say I on the first drop-down list (this one is a simple one) select "Furniture"... I would like the second drop-down list to only show the furniture. Same thing with the third drop-down list, would like only the color of my second choice to be shown there.
Did try to place on the criteria "Custom formula" in the "Data validation" one of this two formulas but does not work...
=FILTER(Object,Type = E2)
or
=QUERY(A:C,"SELECT B WHERE A='"&E2&"' ", 0)
Did read in some other topic here that it was not possible with formulas and I could not find an app script for it. How can i place conditional rules and make only appear the values i want on the drop-down menu instead all of them ? I think it has something to do with the "withCriteria(criteria, args)" however i am not understanding how to apply it.
About the list ... it will be compose maybe with 2k lines (each line 3 columns). First column will only have (maybe) 6 or 7 different values. Second about 70 or 80 and third all different. The order will be random cause new values could be added and i can be adding a new Furniture or Animal ...
This is the code i have now
function onEdit(e) {
var range = e.range;
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
if ( range.getRow() > 1) {
if ( range.getColumn() == 5) {
var cell_Range = ss.getRange( range.getRow(), range.getColumn() + 1);
var cell = cell_Range.getCell( 1, 1);
var rangeV = SpreadsheetApp.getActive().getRange('B2:B13');
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(rangeV).build();
cell.setDataValidation(rule);
}
else if ( range.getColumn() == 6 ) {
var cell_Range = ss.getRange( range.getRow(), range.getColumn() + 1);
var cell = cell_Range.getCell( 1, 1);
var rangeV = SpreadsheetApp.getActive().getRange('C2:C13');
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(rangeV).build();
cell.setDataValidation(rule);
}
}
}
Example sheet at https://docs.google.com/spreadsheets/d/1aLpYd8fC0jpwvQOPVTj_yvY7DVKeFFnPvpJSF27if6w/edit?usp=sharing
Thanks in advance

Solution:
Use Datavalidation requireValueInList
Flow:
Get All options A1:C as a array
Filter A(Col1) if edited value in E(Col5) is present in it
Retrieve corresponding Col2(B) as options and build data validation based on it
Offset the edited range by 1 column and set DataValidation
Sample Script:
function onEdit(e) {
const SETTINGS = {
//Edited Column : Column to Check(First col in optionsDataRange is considered 1)
5: 1,
6: 2,
};
var editedRange = e.range,
editedSheet = editedRange.getSheet(),
val = e.value,
col = editedRange.columnStart,
row = editedRange.rowStart;
/*Exit clause(s)*/
if (
Object.keys(SETTINGS).indexOf(col.toString()) === -1 || //If edited col is not in settings
editedSheet.getName() !== 'Sheet1' ||
row > 5
)
return;
var optionsDataRange = editedSheet
.getRange(1, 1, editedSheet.getLastRow(), 3)
.getValues();
/*Only get options where val is present in optionsDataRange*/
var options = optionsDataRange
.map(function(e) {
return e[SETTINGS[col] - 1] == val ? e[SETTINGS[col]] : null;
})
.filter(function(e, i, a) {
return e !== null && a.indexOf(e) === i;
});
var dv = SpreadsheetApp.newDataValidation()
.requireValueInList(options)
.build();
editedRange.offset(0, 1).setDataValidation(dv);
}
References:
Range
Array

Related

App Script For Loop is Slow Need to optimize code for faster update

This code works perfectly but its slow like turtle. Generally I don't take this approach but I was not able to find any other option.
Well my requirement is to where ever in the column code finds 1 get the key and value of the that index defined range and go to To sheet find the key and paste the value in the required columns.
If I can jump to cells where 1 is in selected column and get the index to find the key and value and then jump to To page key column where key is instead of going to every cell and checking for it whether it is there or not.
I am new to app script and little help would be great.
Thank you in advance.
function Data_Update(){
//assigning sheet name to variables
var ss = SpreadsheetApp.getActive()
var from = ss.getSheetByName("From Sheet")//update here from sheet name(Updated)
var To= ss.getSheetByName("To Sheet")//update here to sheet name(Updated)
// Creating Loop
for (var i = 4;i<=7000;i++){//Update here from which row to start
//assigning values to check if there is 1 in column K
var udaterang = from .getRange("K"+i).getValue()//update here from which column to check for
Logger.log(i)
// Checking condition if the value is diffrent from the value already is
if (udaterang == 1) {
//creating key to find the value
var name1 = from .getRange("A"+i).getValue()//update here the key column 1
var name2 = from .getRange("B"+i).getValue()//update here the key column 2
var name3 = from .getRange("C"+i).getValue()//update here the key column 3
var name = name1.trim()+name2.trim()+name3.trim()
var rng = from .getSheetValues(i,4,1,7)//start row, start column, # rows, # columns
// Looping through each cell to check if the data needs update
for(var j=2;j<=12500;j++){
var key = To.getRange("AP"+j).getValue()
if(key == name){ //[1] because column B
To.getRange("AI"+j+":"+"AO"+j).setValues(rng)
break
}
}
}
}
}
Try this:
function Data_Update() {
const ss = SpreadsheetApp.getActive();
const fsh = ss.getSheetByName("From Sheet");
const fkvs = fsh.getRange(4, 11, fsh.getLastRow() - 3).getValues().flat();
const fvs = fsh.getRange(4, 1, fsh.getLastRow() - 3, fsh.getLastColumn()).getValues();
const tapvs = tsh.getRange(2, 42, tsh.getLastRow() - 1).getValues().flat();
fkvs.forEach((k, i) => {
let udaterang = k;
if (k == 1) {
let name = fvs[i][0].toString().trim() + fvs[i][1].toString().trim() + fvs[i][2].toString().trim();
let idx = tapvs.indexOf(name);
if (~idx) {
tsh.getRange(idx + 2, 35, 1, 7).setValues(tsh.getRange(i + 2, 4, 1, 7).getValues());
}
}
});
}
This may take some tweaking because I have not tested this as I don't have the data to do so.

Sorting by 2 variables

This is a follow up to my previous question that was answered: How to Not Transfer Blank Column to Master Sheet?
2 new questions.
How do I sort by 2 items? I need to sort by Person and their Status (ie Completed). In the status cell is also a date, I just want it to find the completed part.
How to skip if the status is completed? Both of these are for the page that has already been combined. Hope this makes sense.
Sample Sheet: https://docs.google.com/spreadsheets/d/1Y48N8W8CdaNlrxXopj5lnHTiNep33g_qA2-kTrPdt3A/edit#gid=130911536
function combineSheets() {
const sApp = SpreadsheetApp.getActiveSpreadsheet();
const months = ['January','February','March','April','May','June','July',
'August','September','October','November','December'];
const master = sApp.getSheetByName('Master');
const sourceData = [];
months.forEach(m=>{
let sh = sApp.getSheetByName(m);
let vals = sh.getRange(1,2,sh.getLastRow(),27).getValues().filter(r=>r[0]!='');
sourceData.push(...vals);
});
master.getRange(1,1,sourceData.length,sourceData[0].length).setValues(sourceData);
}
Note: In my example, I removed the Other Data columns from your example to make the sheet cleaner. I also added Person column.
Try this:
Sheet1:
Code:
function filternsort() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var sheet3 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet3');
//get sheet1 data
var val = sheet.getDataRange().getValues();
var completed = [];
var notcompleted = [];
//start to 1 to exclude header
for (var i = 1; i < val.length; i++) {
if(val[i][5].toString().match(/Completed.+/)){
//add to array completed if it matches the word Completed in the Date Due column
completed.push(val[i]);
}else{
//add to array notcompleted if not match.
notcompleted.push(val[i]);
}
}
//insert header to the beginning of the array
completed.unshift(val[0]);
//set filtered values
sheet2.getRange(1, 1, completed.length, completed[0].length).setValues(completed);
//sort by Person
sheet2.getRange(2, 1, completed.length, completed[0].length).sort(1);
//insert header to the beginning of the array
notcompleted.unshift(val[0]);
//set filtered values
sheet3.getRange(1, 1, notcompleted.length, notcompleted[0].length).setValues(notcompleted);
//sort by Person
sheet3.getRange(2, 1, notcompleted.length, notcompleted[0].length).sort(1);
}
Sheet2:
Sheet3:
References:
Array.prototype.unshift()
Range.sort()
String.prototype.match()

For loop to check values from two spreadsheets

I have two spreadsheets:
Column A on sheet 6th&7thRoster lists all IDs in a sample, contains 853 items.
Column C on sheet alreadySubmitted contains the IDs of users who've completed a task. Contains 632 items.
I'm trying to parse through both columns. If a user from Column A of sheet 6th&7thRoster matches a user from Column C of sheet sandboxAlreadySubmitted, I want to write the word "Yes" on Column I of the current row of sheet 6th&7thRoster. When using the code below, I'm not seeing not seeing any instances of the word "Yes" on Column I of 6th&7thRoster, even though I know there's multiple places where that should be the case.
function checkRoster() {
var mainSheet = SpreadsheetApp.openById('XXXXXXX');
var roster = mainSheet.getSheetByName('6th&7thRoster');
var submissions = mainSheet.getSheetByName('alreadySubmitted');
var rosterLastRow = roster.getLastRow();
var submissionsLastRow = submissions.getLastRow();
var rosterArray = roster.getRange('A2:A853').getValues();
var submissionsArray = submissions.getRange('C2:C632').getValues;
var i;
var x;
for (i = 1; i < 853; i++) {
for (x = 1; x < 632; x++){
if (rosterArray[i] == submissionsArray[x]){
roster.getRange(i, 9).setValue("Yes");
}
}
}
}
Feedback on how to solve and achieve this task will be much appreciated. For confidentiality, I cannot share the original sheets.
You want to compate the values of A2:A853 of 6th&7thRoster and C2:C632 of alreadySubmitted.
When the values of C2:C632 of alreadySubmitted are the same with the values of A2:A853 of 6th&7thRoster, you want to put Yes to the column "I".
If my understanding is correct, how about this modification? Please think of this as just one of several possible answers.
Modified script:
function checkRoster() {
var mainSheet = SpreadsheetApp.openById('XXXXXXX');
var roster = mainSheet.getSheetByName('6th&7thRoster');
var submissions = mainSheet.getSheetByName('alreadySubmitted');
var rosterLastRow = roster.getLastRow();
var submissionsLastRow = submissions.getLastRow();
var rosterArray = roster.getRange('A2:A853').getValues();
var submissionsArray = submissions.getRange('C2:C632').getValues(); // Modified
// I modified below script.
var obj = submissionsArray.reduce(function(o, [v]) {
if (v) o[v] = true;
return o;
}, {});
var values = rosterArray.map(function([v]) {return [obj[v] ? "Yes" : ""]});
roster.getRange(2, 9, values.length, values[0].length).setValues(values);
}
Flow:
Retrieve values from A2:A853 of 6th&7thRoster and C2:C632 of alreadySubmitted.
Create an object for searching the values from the values of alreadySubmitted.
Create the row values for putting to 6th&7thRoster.
References:
reduce()
map()
If I misunderstood your question and this was not the direction you want, I apologize.

Parse through rotation choices to produce class schedules

I have a Sheets project with 6 worksheets. I'm using student choices to generate a series of 4 rotations in which students can visit classes.
Form Responses 1 lists names in Column D; Columns I-N list a set of six possible rotation choices
6Rot1, 6Rot2, 6Rot3, and 6Rot4 correspond to each of the four possible rotations
mainRoster6 is a master roster produced to reflect names and rotation choices
I'm trying to parse through Form Responses 1, with the following conditions:
Start by looking at Column I and see if there are "open spots" (<47) in the corresponding column of 6Rot1. If so, add the name to that column. Otherwise, check 6Rot2 and try to perform the same operation. Etc., until all 4 rotations have been checked.
If there are no open spots in either of the 4 rotations, then check Column J and try to fit the student in the correct class's name, etc.
The intention is to assign the student's top unique choices to 4 rotations. If a student has already chosen a class for one rotation, then that class should not appear again in a different rotation.
I'm trying to solve two bugs:
If a class has already been added for a student for a rotation, he should not have the same class in a newer rotation (I tried to solve this in lines 62-67 but it didn't work). In the example sheet, for the student in row 3, once the student has been placed in "Spanish I" for rotation 2, it should skip his third choice (a repeated "Spanish I" and proceed trying to place him in "Coding" for his third rotation.
The program should assign in rotations in the order of preference (first, then second, then third, etc.). Right now, if a rotation is full for a specific class, it simply ignores that class (even if the next rotation may be available) and moves on to the next choice. In the example sheet, the first student has "coding" as third choice. Although that class is not available in the 3rd rotation, it's available in the 4th rotation. The program should try to place coding in the 4th rotation and then look at the fourth choice and try to place the student into any available rotation (in this case, rotation 3 would be available).
This is what I have currently:
function myFunction() {
var mainSheet = SpreadsheetApp.openById('1ki0Cya3IWNwdLIBe0fR1vwfz_ansmfa52NilmdFBrd0');
var firstRotation = mainSheet.getSheetByName('6Rot1');
var secondRotation = mainSheet.getSheetByName('6Rot2');
var thirdRotation = mainSheet.getSheetByName('6Rot3');
var fourthRotation = mainSheet.getSheetByName('6Rot4');
var mainRoster = mainSheet.getSheetByName('mainRoster6');
var responsesSheet = mainSheet.getSheetByName('Form Responses 1');
var destinationCell;
var column;
var columnValues;
var columnContainsStudent;
var currentSheet;
var mainRosterLastRow;
var studentName;
var compassTeacher;
var classChoice;
var rotationCounter;
var choicesCounter;
var successCounter;
var rotationOneSuccess;
var rotationTwoSuccess;
var rotationThreeSuccess;
var rotationFourSuccess;
var responsesLastRow = responsesSheet.getLastRow();
var currentRotationsArray;
// var y;
var z;
for (z = 2; z < responsesLastRow + 1; z++) {
studentName = responsesSheet.getRange(z, 4).getValues();
mainRoster.getRange(z, 2).setValue(studentName);
compassTeacher = responsesSheet.getRange(z, 6).getValues();
mainRoster.getRange(z, 1).setValue(compassTeacher);
choicesCounter = 1;
successCounter = 0;
rotationCounter = 1;
rotationOneSuccess = false;
rotationTwoSuccess = false;
rotationThreeSuccess = false;
rotationFourSuccess = false;
while (choicesCounter < 7) {
if (successCounter > 4) {
break;
}
classChoice = responsesSheet.getRange(z, 8 + choicesCounter).getValues();
//test for includes in previous choices
var currentRotationsArray = mainRoster.getRange(z, 4, 1, 4).getValues();
// for (y = 0; y < 4; y++) {
// if (currentRotationsArray[0][y] == classChoice) {
// choicesCounter++;
// break;
// }
// }
while (successCounter < 4) {
if (rotationOneSuccess == false) {
currentSheet = firstRotation;
} else if (rotationTwoSuccess == false) {
currentSheet = secondRotation;
} else if (rotationThreeSuccess == false) {
currentSheet = thirdRotation;
} else {
currentSheet = fourthRotation;
}
// Set column number and get values as an array, depending on class choice
if (classChoice == "Guitar 7") {
column = 1;
columnValues = currentSheet.getRange("A1:A").getValues();
} else if (classChoice == "Communications - Video Production") {
column = 2;
columnValues = currentSheet.getRange("B1:B").getValues();
} else if (classChoice == "Communications - Journalism") {
column = 3;
columnValues = currentSheet.getRange("C1:C").getValues();
} else if (classChoice == "Coding") {
column = 4;
columnValues = currentSheet.getRange("D1:D").getValues();
} else if (classChoice == "Art 7") {
column = 5;
columnValues = currentSheet.getRange("E1:E").getValues();
} else if (classChoice == "Latin I") {
column = 6;
columnValues = currentSheet.getRange("F1:F").getValues();
} else if (classChoice == "Spanish I") {
column = 7;
columnValues = currentSheet.getRange("G1:G").getValues();
} else if (classChoice == "French I") {
column = 8;
columnValues = currentSheet.getRange("H1:H").getValues();
} else {
column = 9;
columnValues = currentSheet.getRange("I1:I").getValues();
}
// Find column's current last row
var columnLength = columnValues.filter(String).length;
//Add the student to the class's rotation, if the student isn't already there and there's >46 people already in
if (columnLength < 48) {
for(var n in columnValues){
if(columnValues[n][0] == studentName){
columnContainsStudent = true;
rotationCounter++;
break;
}
}
if (columnContainsStudent != true) {
destinationCell = currentSheet.getRange(columnLength + 1, column);
destinationCell.setValue(studentName);
if (currentSheet == firstRotation) {
rotationOneSuccess = true;
} else if (currentSheet == secondRotation) {
rotationTwoSuccess = true;
} else if (currentSheet == thirdRotation) {
rotationThreeSuccess = true;
} else {
rotationFourSuccess = true;
}
mainRoster.getRange(z, rotationCounter + 3).setValue(classChoice);
successCounter++;
rotationCounter++;
break;
}
rotationCounter++;
} else {
break
}
rotationCounter++;
}
choicesCounter++;
}
}
}
If you look at the linked sample spreadsheet, the logic is as follows:
We're looking at Form Responses 1 row by row, with the intention of adding 4 "choices" for that person in 4 rotations (6Rot1, 6Rot2, etc.). Columns I-N list 6 possible choices, but ideally I want to put the student in his top 4 choices.
I want the program to first look at Form Responses 1 Column I. In the sample sheet it's "Guitar 7". Then the program should look at 6Rot1 and see if the column for "Guitar 7" (Column A) has any open spaces (i.e. if it has less than 48 rows already filled). In the sample sheet, there are 36 "Placeholder Names" so it adds the first student to the first blank row in Column A (row 37). Once the name has been added to one of the rotations, mainRoster6 reflects that same information (column D in this case reflects "Guitar 7", which contains the same information for 6Rot1).
The program should next look back at Form Responses 1 and consider the student's next choice: in this case it would be the "second choice" (column J, "French I"). I want to look at the first possible rotation (6Rot1, 6Rot2, etc) that doesn't include the student AND has less than 48 people already in it, so that we can put his choice there. In this case6Rot2had already 39 names in it, so it accepted the student into row 40 of column H. After adding the student name in6Rot2, the program adds "French I" to Column E ofmainRoster6` ("Rotation 2")
It next looks back at Form Responses 1 and consider the "third choice" in column K, and repeats the process.
If a class is already "full" (has more than 47 names listed already in the column), then the program should try to find the first available rotation. For example, the student in Form Responses 1 has "Coding" as his third choice. 6Rot3 already has no space for column D ("Coding"), so it should first try to add the student's name ("Billy Kid") in Column D of 6Rot4 and then try to put in the student's name in column I of 6Rot3 for "German I".
What I'm trying to do with the code is parsing row by row of Form Responses 1 and putting the first 4 possible "choices" into the 4 possible rotations, and having mainRoster6 reflect that same information as a kind of abbreviated master list.
If a student lists two or more similar choices in Form Responses 1 columns I-N, then the program should ignore the repeated values. For example, in row 3 of Form Responses 1 it should overlook the second "Spanish" in K3 and move on to the next choice for that student.
If there are no more possible choices or rotations that can account for that choice, then the space can be left blank (see for example Form Responses 1 row 4: based on the student's choices and available spots, we were only able to honor the first three choices ("French" was already filled completely in 6Rot4; "Latin I" and "Art 7" were already in the schedule).
Under these conditions, I would have expected mainRoster6 to have this intended output:

Date Validation with If/Then Function in Google Apps Script

Thanks already to Serge insas for his insight both here and here, which have been a godsend for me already. But...I'm having trouble tying everything together with date validation.
To clarify, I have a GAS intended to verify that the date in Column A is (a) more than seven days old and (b) not null. If both pass, the script determines the first empty row in Column G, and then pauses before completing various functions. The beginning of the script looks like...
function getStats() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Main");
var TITLE_ROW = 1;
var DATE_COL = 1;
var URL_COL = 4;
var sevendaysBefore = new Date(new Date().getTime()-7*24*60*60*1000);
if (DATE_COL != ''||(DATE_COL != null || DATE_COL< sevendaysBefore)) {
var end = sheet.getLastRow();
for( var i = 1; i < end; i++) {
var Gvals = sheet.getRange("G1:G").getValues();
var Glast = Gvals.filter(String).length;
var rowNum = TITLE_ROW+Glast;
var itemurl = sheet.getRange(rowNum,URL_COL).getValues();
Utilities.sleep(500);
...
I've clearly implemented something wrong, though, because the date validation doesn't work—the script appears to function as though the data in Column A doesn't matter. I'm sure I've done something incredibly idiotic, but I'm too ignorant to spot it on my own. So...anyone know what I've overlooked?
While the other answer is probably working (didn't test), its approach is very different from yours.
Below is code that follows the same logic as yours but works at the array level (to follow recommendations in Best practices).
I added a few comments to show the differences, hoping it will help you to understand how it works.
function getStats() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Main");
var Glast; // define the variable for later use
var vals = sheet.getDataRange().getValues();// get all data in an array (do that before loop)
var TITLE_ROW = 0;// use array index instead of real row numbers
var DATE_COL = 0;// use array index instead of real column numbers
var URL_COL = 3;// use array index instead of real column numbers
var sevendaysBefore = new Date(new Date().getTime()-7*24*60*60*1000).getTime();// get native value in milliseconds to make comparison easier below
for( var i = 1; i < vals.length; i++) { // start loop from Row 2 (=array index 1)
if(vals[i][0]!='' && vals[i][0]!=null&&vals[i][0].getTime()<sevendaysBefore){continue};// use && instead of ||, we want ALL conditions to be true ( see !='' and !=null)
Glast = i; break ;// first occurrence of data meeting above condition (non null and date < 7 days before)
}
var itemurl = vals[Glast][URL_COL];// get the value from the array
Utilities.sleep(500);
//...
Mistake : You are hard coding DATE_COL = 1 and you are using this in if statement. It doesn't get the value of the cell. Also I am not getting your statement "date in Column A is (a) more than seven days old". Is that date is from a cell or you are iterating through all the cells in column A ?.
Below code will satisfy your need and I tested. Here as example I am checking date validation for cell R1C1(A1).
1)Get the date from cell. You can change it or Iterate the cells in column for date.
2) We have date.valueOf() method which returns the number of milliseconds since midnight 1970-01-01.
3) Validation : check the cell data is date and greater than 7 days
function compDate()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var cell = sheet.getRange("A1"); //point1
var date01 = new Date();
var date02 = cell.getValue(); //point2
var dateDiff = (date01.valueOf()-date02.valueOf())/(24*60*60*1000);
if((isValidDate(date02)) == true && dateDiff > 7) //point3
Logger.log("success");
}
//below function will return true if the arg is valid date and false if not.
function isValidDate(d) {
if ( Object.prototype.toString.call(d) !== "[object Date]" )
return false;
return !isNaN(d.getTime());
}

Resources