Using ReplaceWith to find & replace text in a google sheet - google-apps-script

This may be a dumb question but I was unable to find an answer on stackoverflow, youtube, or the developers (google) site either for this issue.
I'm trying to use createTextFinder to find a certain word, and replace it with a new word. Ideally I'd like to replace the 2nd instance of the word instead of the first, however if that isn't possible that's OK. I'm also trying to ensure that my function can find these words dynamically instead of resting on defined ranges such as A1:D2 as an example.
So for our example below trying to change the 2nd instance of Apple to Pie.
What I find really bizarre, is that replaceWith doesn't seem to work, but replaceAllWith did work.
Problem:
Unable to have replaceWith work with the createTextFinder method. Receiving the error
"Exception: Service error: Spreadsheets"
Current Sheet:
Expected Outcome:
Troubleshooting I've tried:
Attempted to use startFrom and use a range prior to the 2nd instance of Apple but this didn't seem to work
Attempted to make another textfinder in the same function, however I don't think you're allowed to? I did this in order to do my prior attempt
changed replaceWith to replaceAllWith which worked, but then tried to have it find "Pie" in the same function and change the first instance back to "Apple" but this didn't work
Tried to use the findNext() feature as well, but this unfortunately did not work, and I'm unsure how to use this with the replaceWith method.
Common errors occuring during these attempts is the program stating I do not have a proper function or that the parameters don't match the method signature
Code:
function findText() {
const workSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('sheet1');//I have a few tabs and would like to call to them directly
const tf = workSheet.createTextFinder('Apple');
tf.matchEntireCell(true).matchCase(false);//finds text "Apple" exactly
tf.replaceWith('Pie');
}//end of function findText
Resources:
Google Developers on replaceWith

function replacesecondinstanceofword( word = "Apple",replacement = "Peach") {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet1');
const tf = sh.createTextFinder(word).matchEntireCell(true).findAll();
tf.forEach((f,i) => {
if(i == 1) {
sh.getRange(f.getRow(),f.getColumn()).setValue(replacement)
}
});
}
Learn More

Based on Cooper's solution:
function replace_second(word = "Apple", replacement = "Peach") {
try {
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('sheet1')
.createTextFinder(word).matchEntireCell(true).findAll()[1]
.setValue(replacement);
} catch(e) {}
}

You need to call .findNext() at least once to replace the first matched cell.
function findText() {
const workSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(
'Sheet1'
);
const tf = workSheet.createTextFinder('Apple');
tf.matchEntireCell(true).matchCase(true); //finds text "Apple" exactly
let match = -1;
while (++match < 2) tf.findNext();//for second match
tf.replaceWith('Pie');
}

Related

Is there a way to have a checkbox toggle the text color of a set range?

I have a Google Sheets document and there is a dynamic list that gets created in a certain range (J19:L26) that has some personal data in it. Is there a way to make a checkbox or something quick I can click (even a button?) that can set the text to white or background to black to hide it to onlookers? I currently have the checkbox in cell M17
I have the following code that executes fine but then when I check the box in M17 nothing happens. Maybe I missed a step somewhere? I am new to Google sheets coding. I just wrote the function, tested it Runs, then closed it. Maybe I am just missing a step(s) in implementing the function to my sheet or my function Runs but doesn't do what I need it to?
function Privacy() {
var TheBoard = SpreadsheetApp.getActiveSpreadsheet();
var TheRange = TheBoard.getRange("Board!J19:L26");
if(TheBoard.getRange("Board!M17") == "TRUE")
{
TheBoard.getRange(TheRange).setFontColor('white');
}
}
There are a few things to correct. Here's a list:
When getting the status of the checkbox you're using TheBoard.getRange("Board!M17"). The getRange() method returns a reference to the range, not the value. After getting the range you can use getValue() on it to retrieve the values, so it should be TheBoard.getRange("Board!M17").getValue(), which will return true or false depending on the status of the checkbox.
You're comparing the value of the checkbox to == "TRUE". That makes it a string, and the checkbox value returns a boolean. In Javascript you're supposed to declare booleans as true or false, lowercase without quotes. That means the comparison should be == true instead.
The line TheBoard.getRange(TheRange).setFontColor('white'); returns an error. This is because getRange() expects a range in A1 notation or the coordinates to the row and column, but you're plugging TheRange into it, which is already a Range object. You already defined TheRange, so you don't need to "get" it again. The line should be just TheRange.setFontColor('white');
If you want to make the script run automatically when you click the checkbox you need to set it up as an onEdit() trigger. Then within the trigger check if the field that was edited was the checkbox before proceeding.
It's probably better to create a separate variable for the Sheet to avoid having to specify it in every range call.
May be a nitpick, but capitalizing variables makes the formatting color them like Classes, which may become confusing so I advise not doing that.
That said, here's the script with all the corrections:
function onEdit(e) {
if (e.range.getA1Notation() == "M17") {
var theBoard = SpreadsheetApp.getActiveSpreadsheet();
var theSheet = theBoard.getSheetByName("Board")
var theRange = theSheet.getRange("J19:L26");
if (theBoard.getRange("M17").getValue() == true) {
theRange.setFontColor('white');
} else {
theRange.setFontColor('black');
}
}
}
I recommend you familiarize yourself with the official documentation to better understand how each method works and apply them correctly.
Sources:
Sheet Class
Range Class
Triggers

Can't seem to get any Google Scripts code to work to BOLD all occurrences of one specific word

I found that RichTextValue should work, but I'm not sure where to put the word in on this code and when I use this code
function Bold() {
for (var i = 4; i <=160; i++){
var cell = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(i, 1);
cell.setRichTextValue(SpreadsheetApp.newRichTextValue()
.setText(cell.getValue())
.setTextStyle(2, 16, SpreadsheetApp
.newTextStyle()
.setBold(true)
.build())
.build());
}
}
I get an illegal argument error and I have no idea where I am going wrong. I am not a developer but got pretty good at VBA. Unfortunately I'm really battling with Google Scripts. Any help would be appreciated as this was the only thing I could not get right. Thank you!
Let's consider the following code:
function testBold() {
var boldStyle = SpreadsheetApp.newTextStyle().setBold(true).build();
var cells = SpreadsheetApp.getActive().getRange('Лист1!A1:A100');
var values = cells.getValues(); // initial values
var richValues = values.map( // become rich values
function(v) {
var richBuilder = SpreadsheetApp.newRichTextValue().setText(v[0]);
if (v[0].length && v[0].length > 16) richBuilder = richBuilder.setTextStyle(2, 16, boldStyle);
return [richBuilder.build()];
}
);
cells.setRichTextValues(richValues);
}
At first, we avoid to read data from each cell sequencially, because there is more powerful way to read range data at once.
Further, we use the power of JS map() function to obtain desired rich styling for each input values array element. Do not remember to check input values to have string type and sufficient length before applying the style.
At last we write rich-style data back to the same sheet range.

How can I pass an app script function value (a string) to a google sheet cell as an importXML function directly?

I have many urls I want to pull some info from with importxml on a google sheet.
I'm trying to get review scores of monitors from a website.
I want to create a function so that when I give it a cell as reference (that contains a product's url) it will create a string for an importxml function (with a fixed and proper xpath) and pass it to the cell it is called. (or an adjacent one)
function puan(x) {
var cellFunction = '=IMPORTXML("' + x + '";"//div[#id=\'puan\']//#data-percent")';
return cellFunction;
}
I tried something like this but it didn't work. It just returns the value like a string.
The string looks ok and it returns the value I want if it is directly passed to the cell.
Then I tried this to select the active cell and pass the value there but it didn't work.
I think I'm using it wrong. In the documentation of app script it says custom funcions can only change the value of the cell it is called (or adjacent ones)
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getActiveRange().setValue(cellFunction);
When I add this to my function and delete "return cellFunction;" it returns "0"
I think I'm using it wrong.
Can you guide me because I couldn't find a solution?
I'm also open to suggestions for better ways of extracting same info.
You should be using setFormula() for this purpose: https://developers.google.com/apps-script/reference/spreadsheet/range#setformulaformula
If you still can't get it to work, share a copy of your sheet and I will try to help further.
You have two options:
Either return cellFunction to the main function by calling puan(x) inside the main function
Sample:
function myFunction() {
var URL = "Your URL"
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getActiveRange().setValue(puan(URL));
}
function puan(x) {
var cellFunction = '=IMPORTXML("' + x + '";"//div[#id=\'puan\']//#data-percent")';
return cellFunction;
}
Or set the value into the spreadsheet inside puan(x)
Sample:
function myFunction() {
var URL = "Your URL"
puan(URL);
}
function puan(x) {
var cellFunction = '=IMPORTXML("' + x + '";"//div[#id=\'puan\']//#data-percent")';
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getActiveRange().setValue(cellFunction);
}
Note:
Using setFormula() instead of setValue() is in your case possible, but optional.

Service error: Spreadsheets(line... with .setColumnFilterCriteria

This question is an extension from another.
Apply basic filter to multiple values in a spreadsheet column
I am experiencing an error, specifically Service error: Spreadsheets (line 8, file "Filter") with the following code:
function testFilter() {
var ss = SpreadsheetApp.getActive();
var monthlyDetailSht = ss.getSheetByName("Monthly_Detail");
var filterRange = monthlyDetailSht.getRange(2,12,359,1).getValues(); //Get L column values
var hidden = getHiddenValueArray2(filterRange,["Apple"]); //get values except Apple
var filterCriteria = SpreadsheetApp.newFilterCriteria().setHiddenValues(hidden).build();
var rang = monthlyDetailSht.getDataRange();
var filter = rang.getFilter() || rang.createFilter();// getFilter already available or create a new one
//remove filter and flush
if(monthlyDetailSht.getFilter() != null){monthlyDetailSht.getFilter().remove();}
SpreadsheetApp.flush();
filter.setColumnFilterCriteria(12, filterCriteria);
};
//flattens and strips column L values of all the values in the visible value array
function getHiddenValueArray2(colValueArr,visibleValueArr){
var flatArr = colValueArr.map(function(e){return e[0];}); //Flatten column L
visibleValueArr.forEach(function(e){ //For each value in visible array
var i = flatArr.indexOf(e.toString());
while (i != -1){ //if flatArray has the visible value
flatArr.splice(i,1); //splice(delete) it
i = flatArr.indexOf(e.toString());
}
});
return flatArr;
}
I have used a Logger.log(hidden) to capture the values returned by the function and it is a list of all of the other "fruits" repeated as many times as they are available in column L. I am using fruits as a substitute for the sensitive data.
So here goes my question. Why am I getting that error now when it was working perfectly fine for a couple of days? How can I correct this?
Attempted fixes:
I've tried to add rows to the end of my data. Did not fix.
I tried removing filter, flushing, setting filter. Did not fix. (updated code above with what I did to flush in case anyone else is interested.)
It's working now. A couple of things I want to note for people who stumble upon this with their google searches. First, the issue was in fact an error on Google's side. Using the same code I have above it now works. I did not change it.
Second, I was able to record the filtering through the macro recorder and that code worked when my original code did not. This may help people who are on a time crunch and can't wait for google to get their stuff together. I'm still not sure what specifically in my original code caused the error, but my guess is that it does not matter. I've dedicated a full day to researching this error and it seems sporadic with not a single culprit. My issue may not be the same as yours if it happens in the future.
Hope that helps!

error - "Method Range.getValue is heavily used by the script"

I posted this question previously but did not tag it properly (and hence why I likely did not get an answer) so I thought I would give it another shot as I haven't been able to find the answer in the meantime.
The below script is giving me the message in the title. I have another function which is using the same getValue method but it is running fine. What can I change in my script to avoid this issue?
function trashOldFiles() {
var ffile = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B3:B3").getValue();
var files = DriveApp.getFilesByName(ffile);
while (files.hasNext()) {
var file = files.next();
var latestfile = DriveApp.getFileById(listLatestFile());
if(file.getId() ==! latestfile){
file.setTrashed(true);
}
}
};
Is it an error or an execution hint(the light bulb in the menu)?
are you using that method on other part of your code? probably in listLatestFile()?
I got the same execution hint by calling getRange().getValue() in listLatestFile() (using a loop)
and the hint always mentioned that the problem was when calling
var ffile = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B3:B3").getValue();
in the function trashOldFiles() even when the actual problem was in other function.
Check if you are calling it in other place in your code, probably inside a loop.
OK, so Gerardo's comment about loops started to get me thinking again. I checked some other posts about how to re-use a variable and decided to put the listLatestFile() value in my spreadsheet -
var id = result[0][1];
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B5:B5").setValue(id);
//Logger.log(id);
return id;
and then retrieved the latest file ID from the spreadsheet to use as a comparison value for the trashOldFiles() function which worked a treat.
function trashOldFiles() {
var tfile = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B3:B3").getValue();
var tfiles = DriveApp.getFilesByName(tfile);
var lfile = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B5:B5").getValue();
while (tfiles.hasNext()) {
var tfile = tfiles.next();
if(tfile.getId() !== lfile){
tfile.setTrashed(true);
}
}
};
Not sure if that approach was best practice but it did work for me. If anyone has suggestions for achieving this in a more elegant way, I'm all ears.