Clear conditional formatting using Advanced Sheet Service in Google Apps script - google-apps-script

I am trying to delete conditional formating of a range using below code:
var sheet = range.getSheet();
var address = range.getA1Notation();
var conditionalFormatRules = sheet.getConditionalFormatRules();
var conditionalFormatRule = [];
var sheetId = sheet.getSheetId();
for (let index = 0; index < conditionalFormatRules.length; index++) {
let ranges = conditionalFormatRules[index].getRanges();
for (let j = 0; j < ranges.length; j++) {
if (ranges[j].getA1Notation() == address) {
conditionalFormatRule.push({
"deleteConditionalFormatRule": {
"index": index,
"sheetId": sheetId
}
});
}
}
}
if (conditionalFormatRule.length > 0) {
var spreadsheet = SpreadsheetApp.getActive();
var ssId = spreadsheet.getId();
var format_req = {
"requests": conditionalFormatRule
};
Sheets.Spreadsheets.batchUpdate(format_req, ssId);
}
But it fails with the following exception:
{ [GoogleJsonResponseException: API call to sheets.spreadsheets.batchUpdate failed with error: Invalid requests[8].deleteConditionalFormatRule: No conditional format on sheet: 1876237346 at index: 8] name: 'GoogleJsonResponseException', details: { message: 'Invalid requests[8].deleteConditionalFormatRule: No conditional format on sheet: .... at index: 8', code: 400 } }
It says there is no conditional formatting at index: 8 but there are 11 conditional format rules for that particular range (confirmed by logging the conditional formatting rules)
I want to delete all the conditional formatting rules for a particular range if there is any better way please suggest.
Thanks in advance.

Solved the issue by using getConditionalFormatRules, clearConditionalFormatRules, and setConditionalFormatRules methods of Sheet class.
Get all conditional formatting rules and store it in a variable
existingRules.
Remove the rules from existingRules.
Concat new rules newRules and existingRules.
Clear all conditional formatting rules.
Set all conditional formatting rules allRules to the sheet again.
var existingRules = sheet.getConditionalFormatRules();
var removedRules = [];
for (let index = 0; index < existingRules.length; index++) {
let ranges = conditionalFormatRules[index].getRanges();
for (let j = 0; j < ranges.length; j++) {
if (ranges[j].getA1Notation() == address) {
removedRules.push(existingRules[index]);
}
}
}
for (var i = removedRules.length - 1; i >= 0; i--)
existingRules.splice(removedRules[i], 1);
var newRules = [] //skipping the logic to create new rules
var allRules = existingRules.concat(newRules);
//clear all rules first and then add again
sheet.clearConditionalFormatRules();
sheet.setConditionalFormatRules(allRules);

If you want to update/add new rules to one range, while preserving old rules in the sheet in different ranges, you can achieve that by changing the condition and skipping splicing in your code, like that:
var existingRules = sheet.getConditionalFormatRules();
var rulesToKeep = [];
var rangeToUpdate ="A1:B10"
for (let index = 0; index < existingRules.length; index++) {
let ranges = conditionalFormatRules[index].getRanges();
for (let j = 0; j < ranges.length; j++) {
if (ranges[j].getA1Notation() != rangeToUpdate.getA1Notation()) {
rulesToKeep.push(existingRules[index]);
}
}
}
var newRules = [] //skipping the logic to create new rules
var allRules = rulesToKeep.concat(newRules);
//clear all rules first and then add again
sheet.clearConditionalFormatRules();
sheet.setConditionalFormatRules(allRules);
Very important is to get getA1Notation() for both ranges for comparison, even if your range string is already in A1Notation()

Related

Google Apps Script - Using a Form Response Value in Locating Cells that are duplicates in the sheet and deletes the rows that matches the Value

I've been struggling in looking for a way to delete rows located using the form response which is then compared to a column full of names from previous form submissions in avoiding repeated data in the spreadsheet. The code below is what I have so far. I apologize if the code doesn't make sense as I was trying to fit previous codes given to me. It had a similar concept and I thought it would work somehow but its not working.
var formResponses = form.getResponses();
for (var i = 0; i < formResponses.length; i++) {
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses();
for (var j = 0; j < itemResponses.length; j++) {
var itemResponse = itemResponses[1];
var finalitemResponse = itemResponse.getResponse();
nameofclient_runningbalance.setValue(finalitemResponse);
Logger.log(finalitemResponse);
var values = hospitalSheet.getRange(2, 4, hospitalSheet.getLastRow(), 1).getValues();
Logger.log(values);
var { v, cells } = values.reduce((o, r, i) => {
if (r[4] == finalitemResponse) {
hospitalSheet.getRangeList(cells).deleteRows(cells.length, cells[0].length);
o.cells.push(`R${i + 2}`);
}
return o;
}, { v: [], cells: [] });
if (v.length == 0) return;
}
}
Something like a validation where in it deletes rows located using the form response given name and compared to a column full of Names. There are other ways through it but I want to know if there's a way through the method I am using now for extra knowledge purposes.
I believe your goal is as follows.
You want to delete the rows when the response value of itemResponses[1] is the same as the column "D" of hospitalSheet.
Modification points:
In your script, at var values = hospitalSheet.getRange(2, 4, hospitalSheet.getLastRow(), 1).getValues();, the values are retrieved from the column "D". But, in values.reduce((o, r, i) => {,,,}, r[4] == finalitemResponse is used. In this case, r[4] has no element. And, at hospitalSheet.getRangeList(cells).deleteRows(cells.length, cells[0].length);, cell is not declared.
At nameofclient_runningbalance.setValue(finalitemResponse);, the values are put to the same range.
In your script, I thought that when the delete process is moved to the outside of the loop, the process cost might be able to be reduced.
When these points are reflected in your script, how about the following modification?
Modified script:
var formResponses = form.getResponses();
var resValues = [];
for (var i = 0; i < formResponses.length; i++) {
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses();
for (var j = 0; j < itemResponses.length; j++) {
var itemResponse = itemResponses[1];
var finalitemResponse = itemResponse.getResponse();
resValues.push(finalitemResponse)
// nameofclient_runningbalance.setValue(finalitemResponse);
}
}
resValues = [...new Set(resValues)];
var values = hospitalSheet.getRange(2, 4, hospitalSheet.getLastRow(), 1).getValues();
var rows = values.reduce((ar, [d], i) => {
if (resValues.includes(d)) {
ar.push(i + 2);
}
return ar;
}, []).reverse().forEach(e => hospitalSheet.deleteRow(e));
or,
var formResponses = form.getResponses();
var resValues = [];
for (var i = 0; i < formResponses.length; i++) {
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses();
for (var j = 0; j < itemResponses.length; j++) {
var itemResponse = itemResponses[1];
var finalitemResponse = itemResponse.getResponse();
resValues.push(finalitemResponse)
// nameofclient_runningbalance.setValue(finalitemResponse);
}
}
resValues = [...new Set(resValues)];
var range = hospitalSheet.getDataRange();
var [header, ...values] = range.getValues();
var newValues = [header, ...values.filter(r => !resValues.includes(r[3]))];
range.clearContent();
hospitalSheet.getRange(1, 1, newValues.length, newValues[0].length).setValues(newValues);
When these scripts are run, the values are retrieved from itemResponses[1], and the rows are deleted by searching the retrieved values from the column "D" of hospitalSheet.
Note:
In this modification, it supposes that form and hospitalSheet are declared elsewhere. Please be careful about this.
In this modification, it supposes that from your showing script, the value is searched from column "D" of hospitalSheet. Please be careful about this. If this is different from your actual situation, can you provide the sample Spreadsheet as an image? By this, I would like to confirm it.
If you are required to use nameofclient_runningbalance.setValue(finalitemResponse);, please remove //.
References:
reduce()
filter()

How can Google Sheets Form Update Records from Results Using Google App script?

I have a program that filters and updates data from an existing sheet.
The program works as follows:
1. Find and filter out the required value
2. Enter data in [Adjustment] column then update to database in Record sheet.
I tried to try but my program doesn't seem to work.
I tried to edit the program code but when run it will affect the other columns and the [adjustment] column value is entered wrong.
This is my link program
function Searchold(){
var ss = SpreadsheetApp.getActiveSpreadsheet ();
var shtRecords = ss. getSheetByName ("RECORD");
var shtForm = ss. getSheetByName ("TEST") ;
var records = shtRecords. getDataRange () . getValues ();
var sField = shtForm. getRange ("A3").getValue ();
var sValue = shtForm.getRange ("A6").getValue();
var sCol = records [0].lastIndexOf(sField);
var results = records.filter(function(e){return sValue == e[sCol] });
if(results.length==0){SpreadsheetApp.getUi().alert("not found values");}
else{
shtForm.getRange(9,1,results.length,results[0].length).setValues(results);
}
}
function Updatenew(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var shtRecords = ss.getSheetByName("RECORD");
var shtForm = ss.getSheetByName("TEST");
var LastRow = shtForm.getRange("A8").getNextDataCell(SpreadsheetApp.Direction.DOWN).getLastRow();
var newData = shtForm.getRange(9,1,LastRow -1,7).getValues();
for(var i =0; i<newData.length;i++){
var oldData= shtRecords.getDataRange().getValues();
for(var j= 0;j<oldData.length;j++){
if(newData[i][0] ==oldData[j][0]){
var newData2 = [newData[i]];
shtRecords.getRange(j + 1,1,1,newData2[0].length).setValues(newData2);
}
}
}
}
Can you help me with the update program? Sincerely thank you
Modification points:
When I saw your showing script of Updatenew, I think that each row of var oldData = shtRecords.getDataRange().getValues() is used in each loop of for (var i = 0; i < newData.length; i++) {}. By this, each row is overwritten by each row of newData. By this, all searched rows in "RECORD" sheet are the same value. I thought that this might be the reason for your issue.
var oldData = shtRecords.getDataRange().getValues(); can be used one call.
In order to avoid this issue by modifying your script, as one of several methods, how about the following modification?
From:
for (var i = 0; i < newData.length; i++) {
var oldData = shtRecords.getDataRange().getValues();
for (var j = 0; j < oldData.length; j++) {
if (newData[i][0] == oldData[j][0]) {
var newData2 = [newData[i]];
shtRecords.getRange(j + 1, 1, 1, newData2[0].length).setValues(newData2);
}
}
}
To:
var oldData = shtRecords.getDataRange().getValues();
for (var j = 0; j < oldData.length; j++) {
for (var i = 0; i < newData.length; i++) {
if (newData[0][0] == oldData[j][0]) {
var newData2 = newData.splice(0, 1);
shtRecords.getRange(j + 1, 1, 1, newData2[0].length).setValues(newData2);
break;
}
}
}
Note:
At the above modification, setValues is used in a loop. In this case, the process cost becomes high. If you want to reduce the process cost of the script, how about using Sheets API? When Sheets API is used, how about the following modification? Please enable Sheets API at Advanced Google services.
To
var temp = newData.slice();
var data = shtRecords.getDataRange().getValues().reduce((ar, r, i) => {
if (temp[0][0] == r[0]) {
var t = temp.splice(0, 1);
t[0][2] = Utilities.formatDate(t[0][2], Session.getScriptTimeZone(), "dd/MM/yyyy");
t[0][4] = Utilities.formatDate(t[0][4], Session.getScriptTimeZone(), "dd/MM/yyyy");
ar.push({ range: `'RECORD'!A${i + 1}`, values: t });
}
return ar;
}, []);
Sheets.Spreadsheets.Values.batchUpdate({ data, valueInputOption: "USER_ENTERED" }, ss.getId());

How to change cell values?

I'm using Google app script for sheets and I'm new to it.
I have a column with number and a hyperlink in each cell (each link and number is different).
I want to get the link then make an API request which will return a number which will replace that original number for each cell.
At the moment here is my function:
function getLinkUrls() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("B2:B");
var values = range.getRichTextValues();
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[i].length; j++) {
if (values[i][j].getLinkUrl() !== null) {
const val = values[i][j],
url = val.getLinkUrl(),
path = url.split('/')[3];
var response = UrlFetchApp.fetch(`https://decapi.me/twitch/followcount/${path}`),
f = response.getContentText();
}
}
}
}
I want to replace each cell now with f but I'm unsure how.
Table:
Column B should always have hyperlink
I have successful replace the hyperlink with the follower count, you may try if working on your side also, since I did not use getrichtext but getvalue directly:
function getLinkUrls1() {
var sheet = SpreadsheetApp.getActiveSheet();
for (var i = 2; i <= sheet.getLastRow(); i++) {
var url = sheet.getRange(i,2).getRichTextValue().getLinkUrl();
if (url){
var path = url.split('/')[3];
var response = UrlFetchApp.fetch(`https://decapi.me/twitch/followcount/${path}`),
f = response.getContentText();
sheet.getRange(i,2).setRichTextValue(SpreadsheetApp.newRichTextValue()
.setText(f).build());
}
}
}
This is my sample data:

Unchecking Checkboxes in Google Sheets

For the life of me I can't figure out how set it up. I would like the function to check the entire sheet... alternatively I do know which specific ranges (B5:B26; G5:G26...AF5:AF26) on a specific sheet, if i can't set it up for the entire sheet...
function setFalse()
{
var sheet = SpreadsheetApp.getSheetByName("Test");
var dataRange = sheet.getRange('A:AN28');
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++)
{
for (var j = 0; j < values[i].length; j++)
{
if (values[i][j] == true)
{
values[i][j] = false; // Modified
}
}
}
dataRange.setValues(values);
};
There's a built-in method to uncheck called uncheck(). You can effectively apply it to the sheet by using getDataRange().
function setFalse() {
SpreadsheetApp.getActive().getSheetByName("Test").getDataRange().uncheck();
};
Lastly, looking at your original code, don't forget to get a spreadsheet file before selecting the sheet.

Get row & column indices of found value

How do I get the row and column indices of the cell containing a value I'm looking for?
Here's an example of two sheets, "Grave" and "Data_grave":
My code, below, should...
First, get a specific value in sheet "Grave" (in example value is - "Win").
Find the number of the row & column with this value in sheet "Data_grave".
Finally, it should write some data ("wow") near the found value "Win" (from column+1).
However, I receive an error message at line 17 (the line following my search loops):
Can't convert 4,4 to (class)
How do I solve that?
function myFind() {
var ss = SpreadsheetApp.getActive(), rowNum = [], collNum = [];
var findData = ss.getSheetByName('Grave').getRange("A2").getValue();
var searchData = ss.getSheetByName('Data_grave').getDataRange().getValues();
for(var i=1, iLen=findData.length; i<iLen; i++) {
for(var j=0, jLen=searchData.length; j<jLen; j++) {
for(var k=0, kLen=searchData[0].length; k<kLen; k++) {
var find = findData;
if(find == searchData[j][k]) {
rowNum.push([j+1]);
collNum.push([k+2]);
}
}
}
}
ss.getSheetByName('Data_grave').getRange(rowNum,collNum).setValue("wow");
}
As Adelin commented: the error message is indicating that you are not using .getRange(rowNum,collNum) properly. That method expects two numbers, but you're providing it two arrays.
When you've "found" the cell you're searching for, instead of push() (which treats rowNum and colNum as arrays), you simply want to use:
var rowNum = j+1;
var colNum = k+2;
You could also use a boolean found as an additional exit condition for all your loops, to stop searching upon success.
function myFind() {
var ss = SpreadsheetApp.getActive(), rowNum = [], collNum = [];
var findData = ss.getSheetByName('Grave').getRange("A2").getValue();
var searchData = ss.getSheetByName('Data_grave').getDataRange().getValues();
var found = false;
for(var i=1, iLen=findData.length; i<iLen && !found; i++) {
for(var j=0, jLen=searchData.length; j<jLen && !found; j++) {
for(var k=0, kLen=searchData[0].length; k<kLen && !found; k++) {
var find = findData;
if(find == searchData[j][k]) {
var rowNum = j+1;
var collNum = k+2;
found = true;
}
}
}
}
ss.getSheetByName('Data_grave').getRange(rowNum,collNum).setValue("wow");
}