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

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;
}

Related

Script to automatically change multiple ranges of links

The "master" table has links to cells within that table. These are "I6:I10", "I212:I216", "I418:I422", "I625:I629", "I832:I836", "I1038:I1042".
How can I change these references so that when creating a copy of the "master" table, they refer to cells inside the "master copy"?
Maestro Tanaike wrote a script that works. But I wrote a little incorrectly what is required of the script. Need to modify it so that it processes the ranges "I6:I10", "I212:I216", "I418:I422", "I625:I629", "I832:I836", "I1038:I1042"
function myFunction() {
const checkRange = "I6:I10"; // This is from yoru sample Spreadsheet.
const sheet = SpreadsheetApp.getActiveSheet();
const sheetId = sheet.getSheetId();
const range = sheet.getRange(checkRange);
const richTextValues = range.getRichTextValues().map(r => r.map(c => {
const link = c.getLinkUrl();
return link ? c.copy().setLinkUrl(link.replace(/#gid\=.*&/, `#gid=${sheetId}`)).build() : c;
}));
range.setRichTextValues(richTextValues);
}
Link to table - https://docs.google.com/spreadsheets/d/1Jrpjm7Yjwp-3WTFKyBz87laPeq6ksjwPaaiWWNcIoow/edit?usp=sharing
i tried to put in a script
const checkRange2 = "I212:I216";
but it doesn't work
Use a RangeList, like this:
function replace() {
const sheet = SpreadsheetApp.getActiveSheet();
const sheetId = sheet.getSheetId();
const rangeList = sheet.getRangeList(['I6:I10', 'I212:I216', 'I418:I422', 'I625:I629', 'I832:I836', 'I1038:I1042']);
rangeList.getRanges().forEach(range => modifyLinks_(range, sheetId));
}
function modifyLinks_(range, sheetId) {
const richTextValues = range.getRichTextValues().map(row => row.map(value => {
const link = value.getLinkUrl();
return link ? value.copy().setLinkUrl(link.replace(/#gid\=.*&/, `#gid=${sheetId}`)).build() : value;
}));
range.setRichTextValues(richTextValues);
}

Find and update rows in target worksheet by unique ID and add unique rows if unique ID is not in the target worksheet using Google Apps Script

There is a list in source sheet. Now when the user makes any change to the status/action fields here and clicks "Submit", it should search for a match using the ticket number in column A in target sheet, either update the existing line item with status/action fields from the source or make a new entry in the last row in target sheet.
Lucky that I was able to find a script that is able to make this happen between two tabs in the same sheet, but I'm not able to make it work between different worksheets. Is this something that anyone could help me solve?
function UpdateFunction() {
// 1. Retrieve values from the source and target sheets.
var ss = SpreadsheetApp.getActive();
var [srcSheet, targetSheet] = ['Source', 'Copy of Source'].map(s => ss.getSheetByName(s));
var [srcValues, targetValues] = [[srcSheet, "A2:H"], [targetSheet, "A2:H"]].map(s => s[0].getLastRow() == 1 ? [] : s[0].getRange(s[1] + s[0].getLastRow()).getValues());
// 2. Create objects for searching values of the column "A".
var [srcObj, targetObj] = [srcValues, targetValues].map(e => e.reduce((o, [a, ...b]) => (o[a] = b, o), {}));
// 3. Check update values at the target sheet.
var updatedValues = targetValues.map(([a, ...b]) => [a, ...(srcObj[a] || b)]);
// 4. Check append values.
var appendValues = srcValues.reduce((ar, [a, ...b]) => {
if (!targetObj[a]) ar.push([a, ...b]);
return ar;
}, []);
// 5. Update the target sheet.
var values = [...updatedValues, ...appendValues];
targetSheet.getRange(2, 1, values.length, values[0].length).setValues(values);
}
Thanks in advance,
G
Update sheets from different spreadsheets
function UpdateFunction() {
const ss = SpreadsheetApp.getActive();
const dss = SpreadsheetApp.openById();
const sh1 = ss.getSheetByName("Source");
const vs1 = sh1.getRange("A2:H" + sh1.getLastRow()).getValues();
const sh2 = dss.getSheetByName("Destination");
const vs2 = sh2.getRange("A2:H" + sh2.getLastRow()).getValues();
const id2 = vs2.map(r => r['id2index']).flat();
let d = 0;
vs1.forEach((r,i) => {
let idx = id2.indexOf(r['id1index'])
if(~idx) {
sh2.getRange(idx + 2,1,1,r.length).setValues([r]);
sh1.deleteRow(i + 2 - d++);
}
});
}

Is there an alternative to appendRow in AppsScript that only prints certain columns while leaving others in the row untouched

I'm building a calculator to use for pricing purposes. It has a primary "Calculator" sheet, where an admin can enter data and then generate a new result to the "DataLog" sheet. The "DataLog" sheet stores the results (columns A through X) and calculates the resulting price (columns Y through AO). There are also a few workflow columns that need to be present for each row (Columns AP through AS).
I am currently using appendRow() to print the data to the "DataLog" sheet. The issue is that appendRow() finds the first empty row, and since columns Y through AS are not empty because they contain necessary formulas/workflow, it prints to the bottom of the sheet. I am looking for a way to print the data where 1) it checks only a certain column for an empty row (column A or C, for example) and prints to that row, and 2) does not overwrite the formula/workflow columns (Y through AS).
Is there a way to do this using appendRow() or is there another function I should be using? Other than this one issue of where to print the results, everything works just as I want it to, but I cannot seem to find a way to resolve this issue.
EDIT: The reason the formula and workflow must be present within "DataLog" is that there are situations where after an entry has been filled out and printed changes need to be made to row, thereby changing the final price. So I cannot calculate the price within the function and print that as a static number.
Here is a copy of the calculator: https://docs.google.com/spreadsheets/d/1vsVZeOUUqhdiW1unz6dPuiP5yw24ENrv1-49kXqBnx4/edit#gid=0
Here is a copy of the code I am using:
function ClearCells() {
var sheet = SpreadsheetApp.getActive().getSheetByName('CALCULATOR');
sheet.getRange('G9:H9').clearContent();
sheet.getRange('G11').clearContent();
sheet.getRange('G14:H14').clearContent();
sheet.getRange('G6').clearContent();
sheet.getRange('I6').clearContent();
sheet.getRange('I17:I21').clearContent();
sheet.getRange('I24:I29').clearContent();
sheet.getRange('I32').clearContent();
sheet.getRange('K5').clearContent();
sheet.getRange('K15').clearContent();
}
function FinalizePrice() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sourceRangeFL = ss.getRangeByName('FirstLast');
const sourceValsFL = sourceRangeFL.getValues().flat();
const sourceRangeEN = ss.getRangeByName('EntityName');
const sourceValsEN = sourceRangeEN.getValues().flat();
const sourceRangeEP = ss.getRangeByName('EmailPhone');
const sourceValsEP = sourceRangeEP.getValues().flat();
const sourceRangeRT = ss.getRangeByName('ReturnType');
const sourceValsRT = sourceRangeRT.getValues().flat();
const sourceRangeRE = ss.getRangeByName('Returning');
const sourceValsRE = sourceRangeRE.getValues().flat();
const sourceRangeBQ = ss.getRangeByName('BasicQuestions');
const sourceValsBQ = sourceRangeBQ.getValues().flat();
const sourceRangeSEQ = ss.getRangeByName('SchEQuestions');
const sourceValsSEQ = sourceRangeSEQ.getValues().flat();
const sourceRangeEQ = ss.getRangeByName('EntityQuestions');
const sourceValsEQ = sourceRangeEQ.getValues().flat();
const sourceRangePYP = ss.getRangeByName('PYP');
const sourceValsPYP = sourceRangePYP.getValues().flat();
const sourceRangeADJ = ss.getRangeByName('Adjustment')
const sourceValsADJ = sourceRangeADJ.getValues().flat();
const sourceRangeAN = ss.getRangeByName('AdjustmentNote')
const sourceValsAN = sourceRangeAN.getValues().flat();
const sourceVals = [...sourceValsFL, ...sourceValsEN, ...sourceValsEP, ...sourceValsRT, ...sourceValsRE, ...sourceValsBQ, ...sourceValsSEQ, ...sourceValsEQ, ...sourceValsPYP, ...sourceValsADJ, ...sourceValsAN]
console.log(sourceVals)
const anyEmptyCell = sourceVals.findIndex(cell => cell === "");
if(anyEmptyCell !== -1){
const ui = SpreadsheetApp.getUi();
ui.alert(
"Input Incomplete",
"Please enter a value in ALL input cells before submitting",
ui.ButtonSet.OK
);
return;
}
const date = new Date();
const email = Session.getActiveUser().getEmail();
const data = [date, email, ...sourceVals];
const destinationSheet = ss.getSheetByName("DataLog");
destinationSheet.appendRow(data);
console.log(data);
sourceRangeFL.clearContent();
sourceRangeEN.clearContent();
sourceRangeEP.clearContent();
sourceRangeRT.clearContent();
sourceRangeRE.clearContent();
sourceRangeBQ.clearContent();
sourceRangeSEQ.clearContent();
sourceRangeEQ.clearContent();
sourceRangePYP.clearContent();
sourceRangeADJ.clearContent();
sourceRangeAN.clearContent();
ss.toast("Success: Item added to the Data Log!");
}
I know this is incomplete but for the purpose of discussion here's how I would clear content in your situation.
function ClearCells() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
sh.getRangeList(["G9", "G11", "G14:H14", "G6", "I6", "I17:I21", "I24:I29", "I32", "K5", "K15"]).getRanges().forEach(r => r.clearContent();)
}
If you wished to append the values of your individual ranges into a row you could do it like this:
function appendRangeValues() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const osh = ss.getSheetByName('Sheet1')
const rgl = sh.getRangeList(["G9", "G11", "G14:H14", "G6", "I6", "I17:I21", "I24:I29", "I32", "K5", "K15"]);
const rglb = breakUpRangeList(ss,sh,rgl);
const vs = rglb.getRanges().map(r => r.getValue());
Logger.log(JSON.stringify(vs))
osh.getRange(osh.getLastRow() + 1, 1, 1, vs.length).setValues([vs]);
}
But I'm guessing that you want to skip over cell functions and other columns so let me know what you want and may be we can find a solution that fits your needs
The breakUpRangeList function is something I wrote a while back to break up ranges into their individual cells which I find easier to deal with.
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())
})
})
})
b = [...new Set(b)];
Logger.log(JSON.stringify(b));
return sh.getRangeList(b);
}
Try this:
function appendRangeValues() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const osh = ss.getSheetByName('Sheet1')
const rgl = sh.getRangeList(["G9", "G11", "G14:H14", "G6", "I6", "I17:I21", "I24:I29", "I32", "K5", "K15"]);
const rglb = breakUpRangeList(ss,sh,rgl);
const vs = rglb.getRanges().map(r => r.getValue());
Logger.log(JSON.stringify(vs))
osh.getRange(getColumnHeight(3,osh,ss) + 1, 1, 1, vs.length).setValues([vs]);
}
function getColumnHeight(col, sh, ss) {
var ss = ss || SpreadsheetApp.getActive();
var sh = sh || ss.getActiveSheet();
var col = col || sh.getActiveCell().getColumn();
var rcA = [];
if (sh.getLastRow()){ rcA = sh.getRange(1, col, sh.getLastRow(), 1).getValues().flat().reverse(); }
let s = 0;
for (let i = 0; i < rcA.length; i++) {
if (rcA[i].toString().length == 0) {
s++;
} else {
break;
}
}
return rcA.length - s;
}

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);
}

Dynamic dropdown from another spreadsheet

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);
}