Replace certain cell values in a column - for-loop

Disclaimer: I am Newb. I understand scripting a little, but writing it is a pain for me, mostly with loops and arrays, hence the following.
I am attempting to pull all of the data from a specific column (in this case H [8]), check each cell's value in that column and if it is a y, change it to Yes; if it's n, change it to No; if it's empty, leave it alone and move onto the next cell.
Here's what I have so far. As usual, I believe I'm pretty close, but I can't set the value of the active cell and I can't see where I'm messing it up. At one point I actually changed ever value to Yes in the column (so thankful for undo in these cases).
Example of Sheet:
..... COL-H
r1... [service] <-- header
r2... y
r3... y
r4... n
r5... _ <-- empty
r6... y
Intent: Change all y's to Yes and all n's to No (skip blank cells).
What I've tried so far:
Function attempt 1
function Thing1() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("mySheet");
var lrow = ss.getLastRow();
var rng = ss.getRange(2, 8, lrow - 1, 1);
var data = rng.getValues();
for (var i=0; i < data.length; i++) {
if (data[i][0] == "y") {
data[i][0] == "Yes";
}
}
}
Function attempt 2
function Thing2() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("mySheet");
var lrow = ss.getLastRow();
var rng = ss.getRange(2, 8, lrow - 1, 1);
var data = rng.getValues();
for (var i=0; i < data.length; i++) {
if (data[i][0] == "n") {
data.setValue("No");
} else if (data[i][0] == "y") {
data.setValue("Yes");
}
}
}
Usage:
Once I'm done here, I want to modify the function so that I can target any column and change one value to another (I already have a method for that, but I need to be able to set the value). It would be like so: =replace(sheet, col, orig_value, new_value). I will post it as well below.
Thanks in advance for the help.
Completed Code for searching and replacing within a column
function replace(sheet, col, origV1, newV1, origV2, newV2) {
// What is the name of the sheet and numeric value of the column you want to search?
var sheet = Browser.inputBox('Enter the target sheet name:');
var col = Browser.inputBox('Enter the numeric value of the column you\'re searching thru');
// Add old and new targets to change (Instance 1):
var origV1 = Browser.inputBox('[Instance 1:] What old value do you want to replace?');
var newV1 = Browser.inputBox('[Instance 1:] What new value is replacing the old?');
// Optional - Add old and new targets to change (Instance 2):
var origV2 = Browser.inputBox('[Instance 2:] What old value do you want to replace?');
var newV2 = Browser.inputBox('[Instance 2:] What new value is replacing the old?');
// Code to search and replace data within the column
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheet);
var lrow = ss.getLastRow();
var rng = ss.getRange(2, col, lrow - 1, 1);
var data = rng.getValues();
for (var i=0; i < data.length; i++) {
if (data[i][0] == origV1) {
data[i][0] = newV1;
} else if (data[i][0] == origV2) {
data[i][0] = newV2;
}
}
rng.setValues(data);
}
Hope this helps someone out there. Thanks Again #ScampMichael!

The array named data was created from the values in the range and is independent of the spreadsheet after it is created so changing an element in the array does not affect the spreadsheet. You must modify the array and then put the whole array back where it came from.
for (var i=0; i < data.length; i++) {
if (data[i][0] == "n") {
data[i][0] = "No";
} else if (data[i][0] == "y") {
data[i][0] = "Yes";
}
}
rng.setValues(data); // replace old data with new
}

Related

Add a status to each proccesed row in for loop that has an if statement with Google App Script

I've got a for loop in App script that is looking only at rows that have data in two columns. I'd like to set a status on each row that is actually processed, but the statuses get added to the wrong rows. When I add to i it adds to the whole length of the array, so I guess I shouldn't be trying to process each row, what am I doing wrong?
function auditReport() {
var sheetname = "Sheet1"; // name of data sheet ex. Form Responses 1
var colstoworkon = 10; // how many cols are filled with data f.e. by a form
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.setActiveSheet(ss.getSheetByName(sheetname));
var sheet = ss.getSheetByName(sheetname);
var data = sheet.getRange(3,1,sheet.getLastRow()-1,colstoworkon).getValues(); // starting with row 2 and column 1 as our upper-left most column,
//This makes it loops continuously and checks all not done rows
for (var i in data) {
if(data[i][1] && data[i][2]){//if email or copy are undefined just skip
var setStatus = sheet.getRange(i,4).setValue("done")
} // end of if
} // End of Loop
} //End of email function
Modification points:
In your script, from getRange(3,1,sheet.getLastRow()-1,colstoworkon), in this case, it is required to be getRange(3,1,sheet.getLastRow()-2,colstoworkon).
In the case of for (var i in data) {, i is the string type.
When you want to use sheet.getRange(i,4).setValue("done"), it is required to be sheet.getRange(Number(i) + 3, 4).setValue("done").
I thought that this might be the reason of your issue of but the statuses get added to the wrong rows..
In the case of if (data[i][1] && data[i][2]) {, if the value is 0, data[i][1] && data[i][2] is false.
When these points are reflected to your script, it becomes as follows.
Modified script:
function auditReport() {
var sheetname = "Sheet1";
var colstoworkon = 10;
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.setActiveSheet(ss.getSheetByName(sheetname));
var sheet = ss.getSheetByName(sheetname);
var data = sheet.getRange(3, 1, sheet.getLastRow() - 2, colstoworkon).getDisplayValues();
for (var i in data) {
if (data[i][1] && data[i][2]) {
var setStatus = sheet.getRange(Number(i) + 3, 4).setValue("done");
}
}
}
Or, your script can be also modified as follows. In this modification, done is put using the range list. By this, the process cost can be reduced a little.
function auditReport() {
var sheetname = "Sheet1";
var colstoworkon = 10;
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.setActiveSheet(ss.getSheetByName(sheetname));
var sheet = ss.getSheetByName(sheetname);
var data = sheet.getRange(3, 1, sheet.getLastRow() - 2, colstoworkon).getDisplayValues();
var ranges = data.map(([,b,c], i) => b && c ? `D${i + 3}` : "").filter(String);
if (ranges.length == 0) return;
sheet.getRangeList(ranges).setValue("done");
}
References:
for...in
getRangeList(a1Notations)

Why is this getting the same row twice in second loop?

The code below runs with no apparent errors, but despite I've looked for all possible causes I could think of, I couldn't find the reason why it gets the correct row iterated, marks it as processed ("Sim), and from the 2º iteration on, it gets the new row, but repeats the others already iterated over.
function formToData() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var ss = sheet.getSheetByName("Form Responses 1");
var targetSheet = sheet.getSheetByName("Cadastro de Cliente");
var StartRow = 2;
var RowRange = ss.getLastRow() - StartRow + 1;
var WholeRange = ss.getRange(StartRow, 1, RowRange, 30);
var AllValues = WholeRange.getValues();
var message = "";
for (var i = 0; i < AllValues.length; i++) {
var currentRow = AllValues[i];
//if row has been sent, then continue to next iteration
if (currentRow[0] != "" && currentRow[29] != "Sim") {
//set the row to look at
var setRow = parseInt(i) + StartRow;
var data = currentRow[0];
var dataFormatted = Utilities.formatDate(data, SpreadsheetApp.getActive().getSpreadsheetTimeZone(), "dd/MM/yyyy', às 'HH:mm") + "hs";
//set HTML template for information
message +=
"<p><b>Data: </b>" + dataFormatted + "</p>" +
"<p><b>Unidade: </b>" + currentRow[1] + "</p>"
//mark row as "sent"
ss.getRange(setRow, 30).setValue("Sim");
var values = targetSheet.getRange("A:A").getValues();
var maxIndex = values.reduce(function (maxIndex, row, index) {
return row[0] === "" ? maxIndex : index;
}, 0);
targetSheet.getRange(maxIndex + 2, 1, 1, 30)
.setNumberFormat("#")
.setValues([currentRow]);
var sendTo = "email";
var subject = "Cadastro de cliente novo";
if (message) {
MailApp.sendEmail({
to: sendTo,
subject: subject,
name: "Comercial - Emape",
htmlBody: message,
});
}
}
}//For loop closes
}
I'd appreaciate if you could help me find the flaw.
I tried to replicate this behavior, however, on a static sheet the script performs as expected:
Set unmarked rows in column 30 as 'Sim'.
Copy these rows to a separate sheet starting from the first empty row (or whichever row that Column A is empty).
Given the names of the functions and the sheets, this may be a Sheet generated by a Google Form. These sheets are dynamic, and there is a possibility that the contents may change while your script is running, especially if users are allowed to edit responses.
As a workaround, I suggest to lock the form before running the script:
Also, check the contents of the sheet and form for any possibility that the "Sim" mark might be overwritten by new or edited form data, maybe the sheet is inserting 30 columns instead of 29 or less.

InDesign Text Modification Script Skips Content

This InDesign Javascript iterates over textStyleRanges and converts text with a few specific appliedFont's and later assigns a new appliedFont:-
var textStyleRanges = [];
for (var j = app.activeDocument.stories.length-1; j >= 0 ; j--)
for (var k = app.activeDocument.stories.item(j).textStyleRanges.length-1; k >= 0; k--)
textStyleRanges.push(app.activeDocument.stories.item(j).textStyleRanges.item(k));
for (var i = textStyleRanges.length-1; i >= 0; i--) {
var myText = textStyleRanges[i];
var converted = C2Unic(myText.contents, myText.appliedFont.fontFamily);
if (myText.contents != converted)
myText.contents = converted;
if (myText.appliedFont.fontFamily == 'Chanakya'
|| myText.appliedFont.fontFamily == 'DevLys 010'
|| myText.appliedFont.fontFamily == 'Walkman-Chanakya-905') {
myText.appliedFont = app.fonts.item("Utsaah");
myText.composer="Adobe World-Ready Paragraph Composer";
}
}
But there are always some ranges where this doesn't happen. I tried iterating in the forward direction OR in the backward direction OR putting the elements in an array before conversion OR updating the appliedFont in the same iteration OR updating it a different one. Some ranges are still not converted completely.
I am doing this to convert the Devanagari text encoded in glyph based non-Unicode encoding to Unicode. Some of this involves repositioning vowel signs etc and changing the code to work with find/replace mechanism may be possible but is a lot of rework.
What is happening?
See also: http://cssdk.s3-website-us-east-1.amazonaws.com/sdk/1.0/docs/WebHelp/app_notes/indesign_text_frames.htm#Finding_and_changing_text
Sample here: https://www.dropbox.com/sh/7y10i6cyx5m5k3c/AAB74PXtavO5_0dD4_6sNn8ka?dl=0
This is untested since I'm not able to test against your document, but try using getElements() like below:
var doc = app.activeDocument;
var stories = doc.stories;
var textStyleRanges = stories.everyItem().textStyleRanges.everyItem().getElements();
for (var i = textStyleRanges.length-1; i >= 0; i--) {
var myText = textStyleRanges[i];
var converted = C2Unic(myText.contents, myText.appliedFont.fontFamily);
if (myText.contents != converted)
myText.contents = converted;
if (myText.appliedFont.fontFamily == 'Chanakya'
|| myText.appliedFont.fontFamily == 'DevLys 010'
|| myText.appliedFont.fontFamily == 'Walkman-Chanakya-905') {
myText.appliedFont = app.fonts.item("Utsaah");
myText.composer="Adobe World-Ready Paragraph Composer";
}
}
A valid approach is to use hyperlink text sources as they stick to the genuine text object. Then you can edit those source texts even if they were actually moved elsewhere in the flow.
//Main routine
var main = function() {
//VARS
var doc = app.properties.activeDocument,
fgp = app.findGrepPreferences.properties,
cgp = app.changeGrepPreferences.properties,
fcgo = app.findChangeGrepOptions.properties,
text, str,
found = [], srcs = [], n = 0;
//Exit if no documents
if ( !doc ) return;
app.findChangeGrepOptions = app.findGrepPreferences = app.changeGrepPreferences = null;
//Settings props
app.findChangeGrepOptions.properties = {
includeHiddenLayers:true,
includeLockedLayersForFind:true,
includeLockedStoriesForFind:true,
includeMasterPages:true,
}
app.findGrepPreferences.properties = {
findWhat:"\\w",
}
//Finding text instances
found = doc.findGrep();
n = found.length;
//Looping through instances and adding hyperlink text sources
//That's all we do at this stage
while ( n-- ) {
srcs.push ( doc.hyperlinkTextSources.add(found[n] ) );
}
//Then we edit the stored hyperlinks text sources 's texts objects contents
n = srcs.length;
while ( n-- ) {
text = srcs[n].sourceText;
str = text.contents;
text.contents = str+str+str+str;
}
//Eventually we remove the added hyperlinks text sources
n = srcs.length;
while ( n-- ) srcs[n].remove();
//And reset initial properties
app.findGrepPreferences.properties = fgp;
app.changeGrepPreferences.properties = cgp;
app.findChangeGrepOptions.properties =fcgo;
}
//Running script in a easily cancelable mode
var u;
app.doScript ( "main()",u,u,UndoModes.ENTIRE_SCRIPT, "The Script" );

Google Sheets, Delete 2 or more cells data if another cell has X in it

I have a work log that needs to be cleared at end of day but only if a packer has printed. The department sheets have 2 cells per row, A & G, that need the data cleared if cell H has an X in it. The "Main" sheet is also going to need 4 cells cleared and a formula reset too but if I see the script for the department sheets, I think I will be able to adjust it for the Main sheet.
https://docs.google.com/spreadsheets/d/1L-SK-dWUW_EyX1KxHZLrY_wf-SlxrgvW8tQo5q8ijj4/edit?usp=sharing
I have found a few scripts that will delete the entire row but that's not what I need.
This one I couldn't get to work at all:
function checkH_bb() {
var s, data, colH, colA, key, i;
s=SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Dept 500");
data=s.getDataRange().getValues();
for (i in [1,2,3,4,5,6,7,8,9,10]) data.shift();
colH=data.map(function(x) {return x[7];});
colA=data.map(function(x) {return x[0];});
key="x";
for (i in colH) {
if (colH[i]==key) colA[i]='';
}
s.getRange(11, 1, colA.length).setValues(colA.map(function(x) {return [x];}));
}
This one I didn't try because I realized I needed to keep the formulas.
function readRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (row[1] == 'old') {
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
};
There are formulas in other cells that I need to keep. I looked into keeping the formulas if the data is deleted but I couldn't get that to work either.
This one I couldn't get to work, not sure why.
function myFunction() {
function clearValues(range) {
var formulas = range.getFormulas();
range.clearContent();
range.setFormulas(formulas);
}
}

"for" loop for Google Spreadsheet values not looping

I'm currently writing a statistic spreadsheet script for my guild, which reads out the class of one person and counts it on the statistic sheet.
For some reason the for loops aren't working. When I execute the script, it does nothing. Everything before the for loop seems to work. I have used the debugger, and set a debug point from the point of the for loop and the window is opening and closing after like 1 second.
This is my code as of now:
function addToStatistik() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var source_sheet = spreadsheet.getSheetByName("Raid Montag");
var source_range_names = source_sheet.getRange("C4:C13");
var source_range_setup_boss1 = source_sheet.getRange("M4:M13");
var target_sheet = spreadsheet.getSheetByName("Statistik");
var target_range_names = target_sheet.getRange("A4:A31");
var target_range_boss1 = target_sheet.getRange("K4:S31");
target_sheet.getRange(2,1).setValue("Debug1"); //testing stuff
for (var i=0; i < source_range_names.length; i++) {
for (var j=0; j < target_range_names.length; j++) {
if (source_range_names[i][0] == target_range_names[j][0]) {
if (source_range_setup_boss1[i][0].indexOf("War") > -1) {
target_sheet.getRange(9,5).setValue("TEST");
}
}
}
}
}
Someone can find any errors in there? I can't find anything and google also isnt helping me.
You are getting the range, but not the values. This line:
var source_range_names = source_sheet.getRange("C4:C13");
gets a range, but not any values.
Should be:
var source_range_names = source_sheet.getRange("C4:C13").getValues();
The outer loop never loops. There is no length of a range.
for (var i=0; i < source_range_names.length; i++) {
You don't need to change the above line, but currently the variable source_range_names is a range, and not a 2D array of values.
Before you iterate you need to get the values of the range, to achieve this you need to use the method getValues() or getDisplayValues():
function leFunction() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var source_sheet = spreadsheet.getSheetByName("Raid Montag");
var source_range_names = source_sheet.getRange("A1:C13");
var values_range_names = source_range_names.getDisplayValues();
Logger.log(values_range_names);
for (var i=0; i < values_range_names.length; i++) {
// Do Something
}
}

Resources