Change range in ConditionalFormatRuleBuilder - google-apps-script

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.

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

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

search if value exist in another sheet and display an alert google appscript

I have the following if condition that checks if the a value entered in a form exists in another sheet and if thats the case it will display an alert to the user, but i also wanted to check if the value was found in another sheet that the cell next to it is not empty
var valueToSearch2 = formS.getRange("B5").getValue();
var logSValues = logS.getDataRange().getValues();
if(logSValues.filter(row => row.includes(valueToSearch2)).length)
{
SpreadsheetApp.getUi().alert("Stop Processing");
return
}
Try this:
function checkTheEntireSheet() {
const ss = SpreadsheetApp.getActive();
const formS = ss.getSheetByName('Sheet0');
const vts = formS.getRange("B5").getValue();
const logS = ss.getSheetByName('Sheet1');
let tf = logS.createTextFinder(vts).findAll();
if (tf.length > 0) {
SpreadsheetApp.getUi().alert("Stop Processing");
}
}
const valueToSearch2 = formS.getRange("B5").getValue();
const logSValues = logS.getDataRange().getValues();
const found = logSValues.some(row => {
const foundIndex = row.findIndex(cell=>cell==valueToSearch2)
if(foundIndex<0){
return false // search term not found
}
const emptyRight = !row[foundIndex+1]
const emptyLeft = foundIndex>0 && !row[foundIndex-1]
// additional condition, must be also empty either to the right or to the left
return emptyLeft || emptyRight
})
if(found){
SpreadsheetApp.getUi().alert("Stop Processing");
return
}

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