Dynamic dropdown from another spreadsheet - google-apps-script

With this script I can auto populate a dropdown from a spreadsheet to another but it is limited to import in the dropdown only 500 values, I need it for more values.
In addition, when I run it, it works on the cells that I have selected in the destination sheet.
How to set up a specific range for the dropdown, for example the entire column C?
function importSheetA() {
return SpreadsheetApp.openById('xxxxx')
.getSheetByName('xxxxx')
.getRange('xxxxx')
.getValues()
.flat(); // This ensures a simple array is returned
}
function populateDropdown() {
var values = importSheetA();
var rule = SpreadsheetApp.newDataValidation()
.requireValueInList(values, true)
.setAllowInvalid(false)
.build();
var range = SpreadsheetApp.getActiveRange();
range.setDataValidation(rule);
}

Try this:
function importSheetA() {
const ss=SpreadsheetApp.openById('ssid');
const sh=ss.getSheetByName('Sheet1');
const rg=sh.getRange(1,3,sh.getLastRow());
return rg;//passing range now
}
function populateDropdown() {
const rg=importSheetA();
const rule=SpreadsheetApp.newDataValidation().requireValueInRange(rg).setAllowInvalid(false).build();
const range=SpreadsheetApp.getActiveRange();
range.clearDataValidations();
range.setDataValidation(rule);
}
Perhaps you could implement something like this as a workaround:
function importSheetA() {
const ss=SpreadsheetApp.openById('ssid')
const sh=ss.getSheetByName('Sheet1');
const rg=sh.getRange(1,3,sh.getLastRow());
return rg.getValues();
}
function populateDropdown() {
const ss=SpreadsheetApp.getActive();
const sh=ss.getSheetByName('validationsheet');//local sheet for storing remote validation values
sh.getDataRange().clear();//optional it is noticeably slower
const v=importSheetA();
const rg=sh.getRange(1,1,v.length,1);
rg.setValues(v);
const rule=SpreadsheetApp.newDataValidation().requireValueInRange(rg).setAllowInvalid(false).build();
const range=SpreadsheetApp.getActiveRange();
range.clearDataValidations();
range.setDataValidation(rule);
}

Explanation:
I would like to assume that your list does not consist of more
than 500 unique items. I might well be mistaken though.
The following script will consider the unique values in your data and if the total number of unique values is less than 500, your issue will be resolved.
To find the unique list of values you can use filter:
var uniqueValues = values.filter((v, i, a) => a.indexOf(v) === i);
Solution:
Change populateDropdown to this:
function populateDropdown() {
var values = importSheetA();
var uniqueValues = values.filter((v, i, a) => a.indexOf(v) === i); // <= New code
var rule = SpreadsheetApp.newDataValidation()
.requireValueInList(uniqueValues, true) // <= New code
.setAllowInvalid(false)
.build();
var range = SpreadsheetApp.getActiveRange();
range.setDataValidation(rule);
}

Related

Google Apps Script - How to Update Code to Get Unique Values From Column And Filter Based On Them Instead of Manually Inputting Them

I currently have some code (pasted below) that I use in order to take subsets of a dataset and paste them in separate tabs in the same Google Sheets file. I'm currently manually inputting the values with which I filter the dataset in a list and then looping though each value in the list. I would like to update the code to look through the column and pick up on the unique values in the column and turn the unique values into a list that I would then look through using the rest of the code. I'm having trouble figuring out how to do this though.
Here is a link to the image of my dataset:
enter image description here
Below is my code. I would really like to update the const list = "" part to not be manually inputted anymore but to grab the unique values from the Product Type column (column # 4).
function getSubsetDataComplaints() {
const shName = "RawData";
const list = ["Toy Cars", "Barbie Dolls", "Videogames", "Role Playing Games","Trading Card Games","Food"];
const ss = SpreadsheetApp.getActiveSpreadsheet();
const [headers, ...values] = ss.getSheetByName(shName).getDataRange().getValues()
list.forEach(elem => {
const result = [headers, ...values.filter(r => r[3].includes(elem))]
const sheet = ss.insertSheet(elem);
sheet.getRange(1,1, result.length, result[0].length).setValues(result);
})
}
Try
const shName = "RawData";
const ss = SpreadsheetApp.getActiveSpreadsheet();
const [headers, ...values] = ss.getSheetByName(shName).getDataRange().getValues()
const list = values.map(r => r[3]).flat().filter(onlyUnique).sort()
and add this function
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
your complete code
function getSubsetDataComplaints() {
const shName = "RawData";
const ss = SpreadsheetApp.getActiveSpreadsheet();
const [headers, ...values] = ss.getSheetByName(shName).getDataRange().getValues()
const list = values.map(r => r[3]).flat().filter(onlyUnique).sort()
list.forEach(elem => {
try {
if (elem != '') {
const result = [headers, ...values.filter(r => r[3].includes(elem))]
const sheet = ss.insertSheet(elem);
sheet.getRange(1, 1, result.length, result[0].length).setValues(result);
}
} catch (e) {
Browser.msgBox(e)
}
})
}
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}

App Script: How to set the values of each cell using the .getRangeList?

I am using the .SetValues to attempt to fill every cell I selected through this line var targetSheetRange = targetSheet.getRangeList(arr1);
Unfortunately, when I do it, it always returns me the value of the first cell on all the remaining cells in my Target sheet instead of setting the value of each individual cell from the Source Sheet.
Here's my code:
function filtersCopyData() {
var dTable = SpreadsheetApp.getActiveSpreadsheet();
var sSheetDay = dTable.getSheetByName('Day 1'); // Source Sheet
var sheetRange = sSheetDay.getRangeList(['K3','K4','K5','K6','K7']).getRanges().map(range => range.getValues());
var targetSheet = dTable.getSheetByName('All Filters report'); // Target Sheet
var arr1 = ['B4:C4', 'B6:C6', 'B7:C7', 'B9:C9', 'B10:C10'];
var targetSheetRange = targetSheet.getRangeList(arr1);
targetSheetRange.setValue(sheetRange);
}
K3 value is 9, K4 value is 20, K5 value is 10, K6 value is 10, and K7 value is 10.
targetSheetRange.setValue(sheetRange); When this code is run, all the cells in arr1 return the value of 9, instead of copying the value of each cell from the Source Sheet.
I hope this thing that I'm trying to accomplish does make sense on the code, PS. I'm really a beginner. Thank you everyone!
Description
RangeList is an Array not a Range. I'm suprised your script even ran. You have to use Array.forEach to set the values of the non-contiguous ranges.
You are creating a RangeList and then getting the A1Notation of each range. You can simply define an array of the range A1Notations.
Note, some of the names are different than yours for my test sheet.
Script
function test() {
try {
var spread = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spread.getSheetByName("Data");
var rangeList = ["A1","A3","A5"];
var values = rangeList.map( range => sheet.getRange(range).getValue() );
rangeList = ["C1:D1","C3:D3","C5:D5"];
rangeList.forEach( (range,index) => sheet.getRange(range).setValues([[values[index],values[index]]]));
}
catch(err) {
console.log(err);
}
}
Reference
https://developers.google.com/apps-script/reference/spreadsheet/sheet#getRangeList(String)
https://developers.google.com/apps-script/reference/spreadsheet/range-list#getRanges()
https://www.w3schools.com/jsref/jsref_map.asp
https://www.w3schools.com/jsref/jsref_foreach.asp
Writing to a rangelist with a rangelist of values
function filtersCopyData() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0'); // Source Sheet
const vs = sh.getRangeList(['K3','K4','K5','K6','K7']).getRanges().map(range => range.getValue());
const tsh = ss.getSheetByName('Sheet1');
const arr1 = ['B4:C4', 'B6:C6', 'B7:C7', 'B9:C9', 'B10:C10'];
const rgl = breakUpRangeList(ss,tsh,tsh.getRangeList(arr1));
const l = rgl.getRanges().length;
rgl.getRanges().forEach((r,i) => {
let a1 = r.getA1Notation();
let idx = i % arr1.length;
r.setValue(vs[idx]);
});
}
function breakUpRangeList(ss=SpreadsheetApp.getActive(),sh=ss.getSheetByName("Sheet0"),rgl) {
let b = [];
rgl.getRanges().forEach(rg => {
rg.getValues().forEach((r,i) => {
let row = rg.getRow() + i;
r.forEach((c, j) => {
let col = rg.getColumn() + j;
b.push(sh.getRange(row, col).getA1Notation())
})
})
})
//Logger.log(JSON.stringify(b));
return sh.getRangeList(b);
}

How can I check if a numerical value is within a range of cells in google sheets?

I would like to find if a certain value is in a range using app scripts for google sheets.
var sheet = SpreadsheetApp.getActiveSheet();
var rangeBikeNumbers = sheet.getDataRange("A5:A5000");
var values = rangeBikeNumbers.getValues();
If I have my range rangeBikeNumbers, how can I check if the number "42" for example is in that range. I have searched for hours now and have beeb unable to find any answer to this. indexOf only seems to return -1, regardless of whether or not the value is in the range.
var indexDataNumber = values.indexOf(42); for example always ends up being -1
I believe your goal as follows.
You want to check whether the value of 42 is existing in the range of A5:A5000.
In this case, I would like to propose to use TextFinder. Because when TexiFinder is used, the process cost is low. Ref By the way, getDataRange has not arguments. From your script, I thought that you might want var rangeBikeNumbers = sheet.getRange("A5:A5000");.
When this is reflected to your script, it becomes as follows.
Modified script:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var rangeBikeNumbers = sheet.getRange("A5:A5000");
var find = rangeBikeNumbers.createTextFinder("42").matchEntireCell(true).findNext();
if (find) {
// In this case, the value of 42 is existing in the range.
} else {
// In this case, the value of 42 is NOT existing in the range.
}
}
Note:
About var indexDataNumber = values.indexOf(42); for example always ends up being -1, I think that the reason of this issue is due to that values is 2 dimensional array. If you want to use this, you can also use the following script.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var rangeBikeNumbers = sheet.getRange("A5:A5000");
var values = rangeBikeNumbers.getValues();
var find = values.map(([e]) => e).indexOf(42); // of values.flat().indexOf(42);
if (find > -1) {
// In this case, the value of 42 is existing in the range.
} else {
// In this case, the value of 42 is NOT existing in the range.
}
}
References:
Benchmark: Process Costs for Searching Values in Spreadsheet using Google Apps Script
getDataRange()
getRange(a1Notation)
createTextFinder(findText)
Select any active range that you wish to search and it will search for the seed in that at range. The seed is currently defaulted to 42 but you can change it.
function findSeedInRange(seed = 42) {
const ui = SpreadsheetApp.getUi();
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const rg = sh.getActiveRange();
const row = rg.getRow();
const col = rg.getColumn();
var found = false;
rg.getValues().forEach((r, i) => {
r.forEach((c, j) => {
if (c == seed) {
let r = sh.getRange(i + row, j + col).getA1Notation();
ui.alert(`Found ${seed} in ${r}`);
found = true;
}
})
})
if(!found) {
ui.alert(`Did not find ${seed}`);
} else {
ui.alert('That is all.')
}
}
Here's another approach:
function findSeedInRange() {
const ui = SpreadsheetApp.getUi();
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const rg = sh.getActiveRange();
const resp = ui.prompt('Enter Seed', 'Enter Seed', ui.ButtonSet.OK_CANCEL)
if (resp.getSelectedButton() == ui.Button.OK) {
var seed = parseInt(resp.getResponseText());
const row = rg.getRow();
const col = rg.getColumn();
var found = false;
rg.getValues().forEach((r, i) => {
r.forEach((c, j) => {
if (c == seed) {
let r = sh.getRange(i + row, j + col).getA1Notation();
ui.alert(`Found ${seed} in ${r}`);
found = true;
}
});
});
if (!found) {
ui.alert(`Did not find ${seed}`);
} else {
ui.alert('That is all.')
}
} else {
ui.alert('Operation cancelled.')
}
}

Change range in ConditionalFormatRuleBuilder

Goal: Change range (sheetname, not the a1Notation) inside a ConditionalFormatRuleBuilder.copy()
Error: Conditional format rule cannot reference a different sheet.
I am trying to use the copy method thats is (not so) explained. With the copy i know i have al the arguments for the new conditional formatting i need. Only thing i need to change is the sheetname. Add ranges is working fine, but change/clear the ranges i can't seem to figure out. I found a post, but i want it to make more generic. This example is fine if you know the conditions to work with.
In the docs there is also a .build() is that a option i need in implement?
MainFunction:
function copyFormattingToTargets() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
//const input = SpreadsheetApp.getUi().prompt("Copy formatting from:").getResponseText();
const input = 'Data';
const targets = ['Log','Test'];
const templateSheet = ss.getSheetByName(input);
const inputRules = templateSheet.getConditionalFormatRules();
const rules = convertRules(inputRules,targets);
targets.forEach(target => {
const sheet = ss.getSheetByName(target);
target.clearConditionalFormatRules();
target.setConditionalFormatRules(rules);
})
}
ConvertFunction:
function convertRules(rules,sheetnames){
const output = [];
const ss = SpreadsheetApp.getActiveSpreadsheet();
sheetnames.forEach(sh => {
rules.forEach(rule => {
const copy = rule.copy();
const newRanges = [];
const oldRanges = copy.getRanges();
oldRanges.forEach(range => {
const buildRange = ss.getSheetByName(sh).getRange(range.getA1Notation());
newRanges.push(buildRange);
});
copy.setRanges(newRanges);
output.push(copy);
});
});
return output;
}
When you used ConditionalFormatRuleBuilder.copy(), it will return a ConditionalFormatRuleBuilder type data.
You need to use ConditionalFormatRuleBuilder.build() to generate a ConditionalFormatRule based on your modified range which can be used to set sheet's conditional format rules using Sheet.setConditionalFormatRules()
Example Code: (Copy Conditional Formatting Rules from Sheet1 to Sheet2 and change its range by adding column offset of 2)
function myFunction() {
var sourceSh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var targetSh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2");
var rules = sourceSh.getConditionalFormatRules();
var newRules = [];
rules.forEach((rule, index) => {
Logger.log("****rule****");
var ruleBuilder = rule.copy();
var ranges = ruleBuilder.getRanges();
ranges.forEach(range => {
Logger.log(range.getA1Notation());
})
//Select C1:C1000 as new range
var newRange = targetSh.getRange(1,index + 3,1000,1);
ruleBuilder.setRanges([newRange]);
Logger.log("****new range****");
var ranges = ruleBuilder.getRanges();
ranges.forEach(range => {
Logger.log(range.getA1Notation());
})
Logger.log("****build modified rule****");
var newRule = ruleBuilder.build();
newRules.push(newRule);
});
targetSh.setConditionalFormatRules(newRules);
}
OUTPUT:
Sheet1:
Sheet2:
Your Code:
function convertRules(rules,sheetnames){
const output = [];
const ss = SpreadsheetApp.getActiveSpreadsheet();
sheetnames.forEach(sh => {
rules.forEach(rule => {
const copy = rule.copy();
const newRanges = [];
const oldRanges = copy.getRanges();
oldRanges.forEach(range => {
const buildRange = ss.getSheetByName(sh).getRange(range.getA1Notation());
newRanges.push(buildRange);
});
copy.setRanges(newRanges);
copy.build(); // Build conditional format rules based on modified range
output.push(copy);
});
});
return output;
}
I could be wrong because I don't use format rules much. But it seems to me that rules is an array of individual rule items. And the convertRules() returns that array so:
Perhaps this code might be something like this:
targets.forEach((target,i) => {
const sheet = ss.getSheetByName(target);
target.clearConditionalFormatRules();
target.setConditionalFormatRules(rules[i]);
})
I'm not really sure about this so if I'm wrong then I'll be glad to delete the answer.

Copy values of filter criteria google script

Is it possible to copy values after applying a filter?
I want to ignore the hidden values.
I need to filter a sheet with more than 2000 rows and if I use a loop it takes a long time.
Then, I use this:
var filteredRangefec = range.createFilter()
.setColumnFilterCriteria(6,filterCriteria)
.setColumnFilterCriteria(9, filterCriteriafecha)
.getRange();//range.getFilter().remove();
}
But when i use GetValues take all values, filter and not filter
If you want to retrieve the data and manipulate it in Google Apps Script, you could create temporary sheet, copy filtered data to temporary sheet using method:copyTo() with copyPasteType PASTE_NORMAL and use method:getDataRange() & method:getValues() to retrieve the data.
Example Data:
I copied TheMaster answer here and added some features:
function getFilteredValues(){
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var newSheet = activeSpreadsheet.getSheetByName("Temporary");
//check if existing, delete if yes
if (newSheet != null) {
activeSpreadsheet.deleteSheet(newSheet);
}
//create new sheet with name Temporary
newSheet = activeSpreadsheet.insertSheet();
newSheet.setName("Temporary");
var dataSheet = activeSpreadsheet.getSheetByName("Sheet1");
var toFilter = dataSheet.getDataRange();
var filter = toFilter.createFilter();
//create criteria
var criteria = SpreadsheetApp.newFilterCriteria();
criteria.whenNumberGreaterThan(1200);
//filter first column using the criteria above
filter.setColumnFilterCriteria(1, criteria.build());
//copy filtered data to temporary sheet
var sourceRange = dataSheet.getFilter().getRange();
sourceRange.copyTo(
newSheet.getRange('A1'),
SpreadsheetApp.CopyPasteType.PASTE_NORMAL,
false);
Logger.log(newSheet.getDataRange().getValues());
activeSpreadsheet.deleteSheet(newSheet);
}
Output:
Note: You can also use this to paste the data to sheet.
This will do it. You just pass the range to the removeFilteredData() function and it will return the filtered array.
/**
* #param {SpreadsheetApp.Spreadsheet.Range} range
* #returns {Array<Array>}
*/
function removeFilteredValues(range) {
const values = range.getValues();
const firstRow = range.getRow();
const sheet = range.getSheet();
const filteredValues = values.filter((row, i) => {
return !(sheet.isRowHiddenByFilter(i + firstRow));
});
return filteredValues;
}
function main() {
const range = SpreadsheetApp
.getActiveSpreadsheet()
.getActiveSheet()
.getDataRange();
const result = removeFilteredValues(range);
}
The following is what I use from #Nikko idea above. You can copy the result to different spreadsheet.
function copyFilteredData(sourceID, sheetName, desID, desSheetName, filterCol, filterValue){
const sourceSS = SpreadsheetApp.openById(sourceID);
const sheet = sourceSS.getSheetByName(sheetName);
// remove filter if any
if (sheet.getFilter()) {
sheet.getFilter().remove();
}
var toFilter = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn());
var filter = toFilter.createFilter();
// create criteria
var criteria = SpreadsheetApp.newFilterCriteria();
criteria.whenTextEqualTo(filterValue);
// filter first column using the criteria above
filter.setColumnFilterCriteria(filterCol, criteria.build());
// copy filtered data
const destinationSS = SpreadsheetApp.openById(desID);
const destSheet = destinationSS.getSheetByName(desSheetName);
var sourceRange = sheet.getFilter().getRange();
sourceRange.copyTo(
destSheet.getRange('A1'),
SpreadsheetApp.CopyPasteType.PASTE_NORMAL,
false);
// clear our filter before leaving
if (sheet.getFilter()) {
sheet.getFilter().remove();
}
}