Here is the problem & logic for the find & replace script I am using.
Search Sheet for to_replace string.
If found, replace to_replace with replace_with.
If not found, replace to_replace with to_replace // This is not needed, and causes problems (it replaces all formulas, and replaces it with a string).
My Objective:
I would like the script to only replace cells that match to_replace, and ignore every other cell.
My Rookie Solution:
Exclude specific columns in the foruma by eliminating column C from array using script from here. (only find & replace within Column B & D).
Here is the modified code I added in My Current Script...
const range = sheet.getRange('B2:D'+lastRow).getValues();
range.forEach(a => a.splice(1, 1)); //removes column C.
But I get the error: "TypeError: var data = range.getValues(); is not a function"
Question
Can you help me troubleshoot my rookie solution, or teach me a better way to solve this problem?
My current script
function findAndReplace(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var lastRow = sheet.getLastRow()
var lastColumn = sheet.getLastColumn()
// var range = sheet.getRange(1, 1, lastRow, lastColumn) //REMOVED - Searches all columns.
const range = sheet.getRange('B2:D'+lastRow).getValues(); //ADDED - Searches only B & D
range.forEach(a => a.splice(1, 1)); //ADDED - Searches only B & D
var to_replace = "TextToFind";
var replace_with = ""; //Leave blank to delete Text, or enter text in quotes to add string.
var data = range.getValues();
var oldValue="";
var newValue="";
var cellsChanged = 0;
for (var r=0; r<data.length; r++) {
for (var i=0; i<data[r].length; i++) {
oldValue = data[r][i];
newValue = data[r][i].toString().replace(to_replace, replace_with);
if (oldValue!=newValue)
{
cellsChanged++;
data[r][i] = newValue;
}
}
}
range.setValues(data);
}
From teach me a better way to solve this problem, in your situation, I thought that when TextFinder is used, the process cost might be able to be reduced. When TextFinder is used for achieving your goal, it becomes as follows.
Sample script:
function myFunction() {
var to_replace = "TextToFind";
var replace_with = ""; //Leave blank to delete Text, or enter text in quotes to add string.
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var lastRow = sheet.getLastRow();
var ranges = ['B2:B' + lastRow, 'D2:D' + lastRow];
sheet.getRangeList(ranges).getRanges().forEach(r =>
r.createTextFinder(to_replace).matchEntireCell(true).replaceAllWith(replace_with)
);
}
Note:
If you want to replace the part of cell value, please modify r.createTextFinder(to_replace).matchEntireCell(true).replaceAllWith(replace_with) to r.createTextFinder(to_replace).replaceAllWith(replace_with).
As an additional modification, if your script is modified, how about the following modification?
function findAndReplace() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var lastRow = sheet.getLastRow()
var range = sheet.getRange('B2:D' + lastRow);
var data = range.getValues();
var to_replace = "TextToFind";
var replace_with = ""; //Leave blank to delete Text, or enter text in quotes to add string.
for (var r = 0; r < data.length; r++) {
for (var i = 0; i < data[r].length; i++) {
var value = data[r][i].toString();
if (i != 1 && value.includes(to_replace)) {
data[r][i] = data[r][i].replace(to_replace, replace_with);
}
}
}
range.setValues(data);
}
References:
createTextFinder(findText)
Class TextFinder
Related
I would like to create a simple google apps script to copy specific column into another sheets.
Previously I tried using getLastRow but I get stuck to modify it.
var destinationSheetLastRow = destinationSheet.getDataRange().getLastRow();
Here is my spreadsheet: https://docs.google.com/spreadsheets/d/1rGvmlKCmbjDSCLCC2Kujft5e4ngbSLzJd2NYu0sxISs/edit?usp=sharing
And here is the modified script so far:
function pasteMultiCol(sourceSheet, destinationSheet,sourceColumns,destinationColumns, doneColumn){
var sourceDataRange = sourceSheet.getDataRange();
var sourceDataValues = sourceDataRange.getValues();
var sourcesheetFirstRow = 0;
var sourceSheetLastRow = sourceDataRange.getLastRow();
var destinationSheetLastRow = destinationSheet.getDataRange().getLastRow();
var pendingCount = 0;
//Find the row start for copying
for(i = 0; i < sourceDataValues.length; i++){
if(sourceDataValues[i][doneColumn-1] === "Copied"){
sourcesheetFirstRow++;
};
if(sourceDataValues[i][doneColumn-1] === ""){
pendingCount++;
};
};
//Update Source sheet first row to take into account the header
var header = sourceSheetLastRow-(sourcesheetFirstRow + pendingCount);
sourcesheetFirstRow = sourcesheetFirstRow+header;
// if the first row equals the last row then there is no data to paste.
if(sourcesheetFirstRow === sourceSheetLastRow){return};
var sourceSheetRowLength = sourceSheetLastRow - sourcesheetFirstRow;
//Iterate through each column
for(i = 0; i < destinationColumns.length; i++){
var destinationRange = destinationSheet.getRange(destinationSheetLastRow+1,
destinationColumns[i],
sourceSheetRowLength,
1);
var sourceValues = sourceDataValues.slice(sourcesheetFirstRow-1,sourceSheetLastRow);
var columnValues =[]
for(j = header; j < sourceValues.length; j++){
columnValues.push([sourceValues[j][sourceColumns[i]-1]]);
};
destinationRange.setValues(columnValues);
};
//Change Source Sheet to Copied.
var copiedArray =[];
for(i=0; i<sourceSheetRowLength; i++){copiedArray.push(["Copied"])};
var copiedRange = sourceSheet.getRange(sourcesheetFirstRow+1,doneColumn,sourceSheetRowLength,1)
copiedRange.setValues(copiedArray);
};
function runsies(){
var ss = SpreadsheetApp.openById("1snMyf8YZZ0cGlbMIvZY-fAXrI_dJpPbl7rKcYCkPDpk");
var source = ss.getSheetByName("Source");
var destination = ss.getSheetByName("Destination");
var sourceCols = [4,5,6,7,8,9,10];
var destinationCols = [7,8,9,10,11,12,13];
var doneCol = 12
//Run our copy and append function
pasteMultiCol(source,destination, sourceCols, destinationCols, doneCol);
};
Your code is taken from my tutorial in my blog article Copy Selected Columns in One Sheet and Add Them To The Bottom of Different Selected Columns in Another Sheet and it just needs a tweak.
I think the issue might be that you have a bunch of formulas in other columns in your "Destination" Sheet tab. So getting the last row of the sheet will result in getting the last row considering all the data including your other formulas.
You might find this explanation in a follow up blog article I wrote helpful: Google Apps Script: Get the last row of a data range when other columns have content like hidden formulas and check boxes
In short, you can change the destinationSheetLastRow variable to something simple like this.
var destinationSheetLastRow = (()=>{
let destinationSheetFirstRow = 7; // The first row of data after your header.
//Get a sample range to find the last value in the paste columns.
let sampleRange = destinationSheet.getRange(destinationSheetFirstRow,
destinationColumns[0],
destinationSheet.getLastRow())
.getValues();
let sampleLastRow = 0;
while(sampleLastRow < sampleRange.length){
if (sampleRange[sampleLastRow][0] == ""){
break;
}
sampleLastRow++;
};
return sampleLastRow;
})()
I have used the example code from this link, to make the code below.
https://yagisanatode.com/2017/12/13/google-apps-script-iterating-through-ranges-in-sheets-the-right-and-wrong-way/
Most of the code is working as expected, except for the last row.
In Column E, I want to place the custom function =apiav() with the data from cell A.
However the code is returning =apiav(Range) in the Google sheet cells. (to be clear it should be something like =apiav(a1))
I tried everything i could think of and of course googled for hours, but i am really lost and can't find the right solution for this.
function energy(){
var sector = "Energy";
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var rangeData = sheet.getDataRange();
var lastColumn = 2;
var lastRow = 999 ;
var sheet = ss.getSheets()[0];
var searchRange = sheet.getRange(2,2, lastRow-1 ,1 );
var ouputrange = sheet.getRange(2,4, lastRow-1 ,1 );
//clear range first
ouputrange.clear("D:D");
ouputrange.clear("E:E");
/*
GOOD - Create a client-side array of the relevant data
*/
// Get array of values in the search Range
var rangeValues = searchRange.getValues();
// Loop through array and if condition met, add relevant
// background color.
for ( i = 0; i < lastColumn - 1; i++){
for ( j = 0 ; j < lastRow - 1; j++){
if(rangeValues[j][i] === sector){
sheet.getRange(j+2,i+4).setValue("yes");
var formularange = sheet.getRange (j+2,i+1);
sheet.getRange(j+2,i+5).setValue('=apiav(' + formularange + ')');
}
};
};
};
Replace:
var formularange = sheet.getRange(j+2,i+1);
with:
var formularange = sheet.getRange(j+2,i+1).getA1Notation();
So you will be able to pass the cell reference instead of the range object.
This is just a snippet of my code from Google App Script which iterates through each row in columns 1, 2, 3. If an edit is made in column 3, an incremental ID will be generated and a concatenation of the same row and different columns will also be generated - in this case Column D, E, and F. I am struggling with figuring out a way to change the formulas into values. What am I missing here?
// Location format = [sheet, ID Column, ID Column Row Start, Edit Column]
var locations = [
["Consolidated Media Plan",1,9,3]
];
function onEdit(e){
// Set a comment on the edited cell to indicate when it was changed.
//Entry data
var range = e.range;
var col = range.getColumn();
var row = range.getRow();
// Location Data
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
function getNewID(){
function IDrange(){
var dataRange = sheet.getDataRange();
var lastRow = dataRange.getLastRow();
return sheet.getRange(IDrowStart,IDcol,lastRow-IDrowStart).getValues();
};
//Get largest Value in range
function getLastID(range){
var sorted = range.sort();
var lastIDval = sorted[sorted.length-1][0];
return lastIDval;
};
//Stores leading letters and zeroes and trailing letters
function getLettersNzeroes(id){
//Get any letters or zeroes.
var re = new RegExp("^([a-zA-Z0])$");
var letterZero = [];
for(char = 0; char < id.length; char++){
if(re.test(id[char])){
letterZero.push([char,id[char]]);// [[position, letter or zero]]
};
};
// Categorize letters and zeroes into start and end blocks
var startLetterZero = "",
endLetter = "",
len = letterZero.length - 1;
for(j = 0; j < letterZero.length; j++){
if(letterZero[j][0] === j){
startLetterZero += letterZero[j][1];
}else if(letterZero[j][1] !== "0" && letterZero[len][0] - (len - j) == letterZero[j][0]){
endLetter += letterZero[j][1];
};
};
var startNend = {"start":startLetterZero,"end":endLetter};
return startNend;
};
//Gets last id number. Adds 1 an checks to set if its new length is greater than the lastNumber.
function getNewNumber(id){
var removeZero = false;
var lastNum = parseInt(id.replace(/\D/g,''),10);//Remove letters
var newNum = (lastNum+1).toString();
if(lastNum.toString().length !== newNum.length){
var removeZero = true;
};
var newNumSet = {"num":newNum, "removeZero": removeZero};
return newNumSet
};
var lastID = getLastID(IDrange());
var lettersNzeroes = getLettersNzeroes(lastID);
var newNumber = getNewNumber(lastID);
//If the number is 9,99,999,9999 etc we need to remove a zero if it exists.
if(newNumber.removeZero === true && lettersNzeroes.start.indexOf("0") !== -1.0){
lettersNzeroes.start = lettersNzeroes.start.slice(0,-1);
};
//Rejoin everything together
var newID = lettersNzeroes.start +
newNumber.num +
lettersNzeroes.end;
return newID;
};
for(i = 0; i < locations.length; i++){
var sheetID = locations[i][0],
IDcol = locations[i][1],
IDrowStart = locations[i][2],
EditCol = locations[i][3];
var offset = IDcol - EditCol;
var cell = sheet.getActiveCell();
if(sheetID === sheet.getName()){
if(EditCol === col){
//ID Already Exists the editing cell isn't blank.
if(cell.offset(0,offset).isBlank() && cell.isBlank() === false){
var newID = getNewID();
cell.offset(0,offset).setValue(newID);
cell.offset(0,-1).setFormulaR1C1('=concatenate(R[0]C[-1],"_",INDEX(Glossary!K:K,MATCH(R[0]C[2],Glossary!J:J,0)))');
};
};
};
};
};
EDIT:
This is my full code, I have been unsuccessful with trying to retrieve just the values of the formula within the same (i.e, If C9 gets edited, a formula with the values specific to the 9th row should be populated)
Also, I've tried to add an index/match formula to the concatenation formula at the bottom of the code - it works as expected on the google sheets, but when I run it with the script it pastes the correct formula but it returns a #NAME? error message. However, when I copy and paste the exact same formula in the cell, it works perfectly, any idea what could be causing this error?
This works for me. I know it's not exactly the same thing but I didn't have access to getNewId()
function onEdit(e) {
var sh=e.range.getSheet();
if(sh.getName()!='Sheet1')return;
//e.source.toast('flag1');
if(e.range.columnStart==3 && e.range.offset(0,1).isBlank() && e.value) {
//e.source.toast('flag2');
e.range.offset(0,1).setValue(e.value);
e.range.offset(0,2).setFormulaR1C1('=concatenate(R[0]C[-1],"_",R[0]C[-2],"_",R[0]C[-3],"_",R[0]C[-4])');
}
}
I've been working on a script for my google sheet that takes a full string from a cell and compared them against a list of keywords on a second sheet, this should return the column number. There is more to go, but i'm having some trouble.
I have tried a few methods, and now I'm trying regex. I think I'm using it correctly. My logic is if the regex expression of the keywords matches any full word of the original query then return true and the column number.
I have got the cells in the "Tag's" sheet offset,. but its checking the right ranges.
function returnTag(narration){
var main = SpreadsheetApp.getActiveSpreadsheet();
var tagsheet = main.getSheetByName('Tags');
var numRows = tagsheet.getLastRow();
var numCols = tagsheet.getLastColumn();
var range = tagsheet.getRange(6, 2, numRows-5, numCols-1);
var row;
var col;
for (var col = 1; col <= numCols-1; col++) {
for (var row = 1; row <= numRows-5; row++) {
var currentValue = range.getCell(row,col).getValue();
var regExp = new RegExp("\b"+narration+"\b","gi");
if(regExp.test(currentValue)) {
return(col);
}
else{ }
}
}
}
I expect that if i type "There are plenty of fish in the sea" in A2 of the first sheet, it will return 2 as the column reference it was found in A1 of the first sheet.
I have shared a editable copy of the sheet below.
https://docs.google.com/spreadsheets/d/19FDT6ximWg4AcGzDTuqC4AYIW4MgXFwlUH0wYfGQi8s/edit?usp=sharing
Thanks :)
Couple things:
The new RegExp() ought to contain the currentValue and test the "narration" (your original code is doing the reverse)
You don't need the word boundary \b within the new RegExp() function
Try this -
function returnTag(narration){
var main = SpreadsheetApp.getActiveSpreadsheet();
var tagsheet = main.getSheetByName('Tags');
var numRows = tagsheet.getLastRow();
var numCols = tagsheet.getLastColumn();
var range = tagsheet.getRange(6, 2, numRows-5, numCols-1);
var row;
var col;
for (var col = 1; col <= numCols-1; col++) {
for (var row = 1; row <= numRows-5; row++) {
var currentValue = range.getCell(row,col).getValue();
var regExp = new RegExp(currentValue,"gi");
if(regExp.test(narration)) {
return(col);
}
else{ }
}
}
}
I've updated the same in the sheet that has been shared too & it works now :)
I'm new to Google Apps Script & I'm trying to make a script for a spreadsheet where I can get the range for the matched value. But there's no function for the cell value like .getRow() or .getRange() or something like this since it's a string value. Here are my codes,
function getInfo() {
var ss1 = sheet1.getSheetByName("Sheet1");
var ss2 = sheet2.getSheetByName("Rates");
var getCountry = ss1.getRange(ss1.getLastRow(), 11).getValue();
var countryRange = ss2.getRange(2, 1, ss2.getLastRow(), 1).getValues();
var getZone = 0;
for (var i = 0; i < countryRange.length; i++) {
if (countryRange[i] == getCountry) {
var getrow_cr = countryRange[i].getRow(); //.getRow() doesn't apply here for string value
getZone = ss2.getRange(getrow_cr + 1, 2).getValue();
}
}
Logger.log(getZone);
}
How can I get the row for the matched string so that I can get the cell value beside the matched cell value?
Your looping variable i starts at 0, which in your case is equivalent to Row 2. To help keep that clear, you could use a named variable to track the first row with data:
var firstrow = 2;
Then when you find a match, the spreadsheet row it's in is i + firstrow.
Updated code:
function getInfo() {
var ss1 = sheet1.getSheetByName("Sheet1");
var ss2 = sheet2.getSheetByName("Rates");
var getCountry = ss1.getRange(ss1.getLastRow(), 11).getValue();
var firstrow = 2;
var countryRange = ss2.getRange(firstrow, 1, ss2.getLastRow(), 1).getValues();
var getZone = 0;
for (var i = 0; i < countryRange.length; i++) {
if (countryRange[i][0] == getCountry) {
var getrow_cr = i + firstrow;
getZone = ss2.getRange(getrow_cr, 2).getValue();
break; // Found what we needed; exit loop
}
}
Logger.log(getZone);
}
Since countryRange is a two-dimensional array with one element in each row, the correct way to reference the value in row i is countryRange[i][0]. If you instead just compare countryRange[i], you are relying on the JavaScript interpreter to coerce the "row" into a String object.