Set formula for adjacent cell if text is present - google-apps-script

I'm working with a Google Sheets form which also accepts answers via text message. I'm trying to work out a method using Google Apps Scripts to split the body of the text message using a comma as a delimiter.
The problem I'm running into is overwriting information submitted by the form and not by text message.
My current script is:
function splitCells() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var colC = sheet.getRange("C2:C").getValues();
var colD = sheet.getRange("D2:D").getFormulas();
//Logger.log(colC);
for(var i in colC){
if(typeof(colC[i][0]) =='string'){
colD = '=if(istext(C2:C),split(C2:C,",",true))';
} else {
colD = 'D2:D';
}
}
sheet.getRange("D2:D").setFormula(colD);
}
The function is working correctly, splitting the contents of column C (the SMS body) into D, E, and F as expected. But, it's overwriting data in column D because the else condition isn't being met (colC is blank in those places).
How do I get the script to move over blank cells without replacing the contents of the cell?
It's sort of confusing to explain, so here's a sample document you can check out. A custom menu should install when you open it and you can run the script from there (or from the editor).
Thanks for the help.

There are a few simple mistakes to start.
A spreadsheet cell can contain a value or a formula, not both.
If you use setFormula/s(), any value in a cell will be replaced by the result of the formula, even if the formula is blank.
Since you want to have a mix of values and formulas, you should set formulas only in the specific cells that match the criteria:
// If we received a SMS response, set a formula to parse it
sheet.getRange(2+i,4).setValue('=if(istext(C2:C),split(C2:C,",",true),"")')
The criteria test isn't sufficient. A blank cell is still of type string, but it's a blank string. So this evaluates true for both form entries and SMS entries:
if(typeof(colC[i][0]) =='string'){ ...
A more effective test checks for a non-blank response:
if(colC[i][0] != ''){ ...
An even better one would ensure that the value in column C meets the required format requirements.
You are looping over an array using the for .. in loop, which is meant for going over object properties. This works, but the loop value i will be a string, which can cause problems when doing math. Better to get in the habit of looping over the numeric index. (See.)
The full-column range expression C2:C is elegant, however you end up with an array that contains all rows in the spreadsheet, more than a thousand in your example. Since we're going to loop over all rows, it's best to limit that range:
var colC = sheet.getRange(2, 3, sheet.getLastRow()).getValues(); // C2:C, only non-blank rows
Adjusting for those problems, we have:
function splitCells2() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var colC = sheet.getRange(2, 3, sheet.getLastRow()).getValues(); // C2:C, only non-blank rows
//Logger.log(colC);
for(var i=0; i< colC.length; i++){
if(colC[i][0] != ''){
// If we received a SMS response, set a formula to parse it
sheet.getRange(2+i,4).setValue('=if(istext(C2:C),split(C2:C,",",true),"")')
}
}
}

Related

Using for and if loops in Google Apps Script

Dear programming Community,
at first I need to state, that I am not quite experienced in VBA and programming in general.
What is my problem? I have created a topic list in google sheets in order to collect topics for our monthly meeting among members in a little dance club. That list has a few columns (A: date of creation of topic; B: topic; C: Name of creator; ...). Since it is hard to force all the people to use the same format for the date (column A; some use the year, others not, ...), I decided to lock the entire column A (read-only) and put a formular there in all cells that looks in the adjacent cell in column B and sets the current date, if someone types in a new topic (=if(B2="";"";Now()). Here the problem is, that google sheets (and excel) does then always update the date, when you open the file a few days later again. I tried to overcome this problem by using a circular reference, but that doesn't work either. So now I am thinking of creating a little function (macro) that gets triggered when the file is closed.
Every cell in Column B (Topic) in the range from row 2 to 1000 (row 1 is headline) shall be checked if someone created a new topic (whether or not its empty). If it is not empty, the Date in the adjacent cell (Column A) shall be copied and reinserted just as the value (to get rid of the formular in that cell). Since it also can happen, that someone has created a topic, but a few days later decides to delete it again, in that case the formular for the date shall be inserted again. I thought to solve this with an If-Then-Else loop (If B is not empty, then copy/paste A, else insert formula in A) in a For loop (checking rows 1 - 1000). This is what I have so far, but unfortunately does not work. Could someone help me out here?
Thanks in advance and best regards,
Harry
function NeuerTest () {
var ss=SpreadsheetApp.getActive();
var s=ss.getSheetByName('Themenspeicher');
var thema = s.getCell(i,2);
var datum = s.getCell(i,1);
for (i=2;i<=100;i++) {
if(thema.isBlank){
}
else {
datum.copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}}
}
The suggested approach is to limit the calls to the Spreadsheet API, therefore instead of getting every cell, get all the data at once.
// this gets all the data in the Sheet
const allRows = s.getDataRange().getValues()
// here we will store what is written back into the sheet
const output = []
// now go through each row
allRows.forEach( (row, ind) => {
const currentRowNumber = ind+1
// check if column b is empty
if( !row[1] || row[1]= "" ){
// it is, therefore add a row with a formula
output.push( ["=YOUR_FORMULA_HERE"] )
} else {
// keep the existing value
output.push( [row[0]] )
}
})
Basically it could be something like this:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Themenspeicher');
var range = sheet.getRange('A2:B1000');
var data = range.getValues(); // <---- or: range.getDisplayValues();
for (let row in data) {
var formula = '=if(B' + (+row+2) + '="";"";Now())';
if (data[row][1] == '') data[row][0] = formula;
}
range.setValues(data);
}
But actual answer depends on what exactly you have, how your formula looks like, etc. It would be better if you show a sample of your sheet (a couple of screenshots would be enough) 'before the script' and 'after the script'.

Fetch text string from specific column and select range of that string

I have shared below link of my sheet.
I tried every possible script to accomplish below task in the end in frustration i wipe out my whole script.
I would like to match text from column A and return or getValue of corresponding B column.
So I can use that getValue from its corresponding B column for further arithmetic operations.
Thank you.
sheet link - https://docs.google.com/spreadsheets/d/1SwYYacz9A9s6ZXrL44KtRN7gqnwXkhu4cDILdUA1iJQ/edit?usp=sharing
Basic steps:
Retrieve your data range
Form an 1D array out of your column A entries, e.g. with map()
Check either the search string is contained in the array - and if yes retrieve its position - e.g. with indexOf()
Retrieve the value with the respective row index in column B
Sample:
function myFunction() {
var matchText = "C";
var values = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getDataRange().getValues();
var columnA =values.map(function(e){return e[0]});
var row = columnA.indexOf(matchText);
if (row >= 0){
var Bvalue = values[row][1];
Logger.log(Bvalue);
}
}
I encourage you to take some time to study Apps Script, so you cannot only understand this code and adapt to your needs, but also write your own scripts.

I want to take multiple cells, and what ever cell is not FALSE I want that cell to be extracted and moved to another column in a different sheet

I am working on an action form. There are actions that can be ticked by boxes. If the boxes are not ticked they leave the cell with a value FALSE. I want any cell (B6,B7,E7) to be copied to a different cell if the cell does not read FALSE.
I am attempting this on google sheets script editor with an if statement.
var submit_type_range = submit_sheet.getRange('B6:B7');
var types =submit_type_range.getValues();
if (types != 'FALSE')
var type = types
return type
var type_submitted = log_sheet.getRange(lastRow_log+1,3)
type_submitted.setValue(type)
I know this won't work but I expected at least type to extract all cells that do not display FALSE.
To accomplish what you want to do we had to rewrite almost the full function; we apologize if the following code doesn't meet your expectations.
The code will first read the values of the cells B6, B7 and E7. As .getValues() A1 notation only works for continuous cells, we are going to concatenate the E7 data to the others cells.
After that we are going to iterate each cell and check if its content is false or not. We wrote it in lower case because it's a reserved keyword, so every FALSE get converted to false in our reading array.
Finally, we write the data of those cells on the last empty row. As we didn't understand which column of the last empty row you want the data in, we choose to write each cell in the column that match the index of the original reading (i.e. B6 🠦 Column A, B7 🠦 Column B and E7 🠦 Column C).
As an extra, we made one last loop that deletes the elements of the types array which are false. We added this because of your last phrase.
function falseRemover() {
var spreadsheet = SpreadsheetApp.openById('{SPREADSHEET ID}').getSheetByName(
'Sheet1');
var types = spreadsheet.getRange('B6:B7').getValues().concat(spreadsheet
.getRange('E7').getValues());
var lastRow = spreadsheet.getLastRow() + 1;
var columnIterator = 0;
for (var cell in types) {
columnIterator++;
if (types[cell] != 'false') {
spreadsheet.getRange(lastRow, columnIterator).setValue(types[cell]);
}
}
for (var cell in types) {
if (types[cell] == 'false') {
types[cell].pop();
}
}
}
We hope that this code example is useful to you, but don't hesitate to ask for more help if you need it.

Google Sheets OnEdit script: copy value from specific columns

We are using Google sheets for tracking attendance. Previously, the teachers were entering P, T, or A (for present, tardy, absent) for each period. I would still like users to have the option to enter a value for each period in a week, however it would be a great time saver if they could enter one value for the whole day.
What I'd like is that if a value is entered into any one of the "0" periods (green columns) with a "P" or "A" (data validation limits those options) an OnEdit function would copy that same letter ("P" or "A") to the following 8 columns and then delete the original value. (without the deletion the totals on the far right columns will be off). I would not want the OnEdit to be activitated based on edits in any of the non-green columns.
I will eventually have several tabs, each one a different week, but each exactly the same... so I'm thinking the function should work within whatever the activesheet is.
https://docs.google.com/spreadsheets/d/1NKIdNY4k66r0zhJeFv8jYYoIwuTq0tCWlWin5GO_YtM/edit?usp=sharing
Thank you for your help,
I wrote some code to get you started with your project. (I am also a teacher) You will have to make some changes based on what you are going for and it can probably be optimised to run faster. Good luck!
function onEdit(e) {
//create an array of the columns that will be affected
var allColumns = [2, 10];
//get the number values of the column and row
var col = e.range.getColumn();
var row = e.range.getRow();
//get the A1 notation of the editted cell for clearing it out
var cell = e.range.getA1Notation();
//only run if the cell is in a column in the allColumns array
if(allColumns.indexOf(col) > -1) {
//run the for loop for the next 8 cells
for(var i = col + 1; i < col + 9; i++) {
SpreadsheetApp.getActiveSheet().getRange(row, i).setValue(e.value);
SpreadsheetApp.getActiveSheet().getRange(cell).setValue('');
}
}
}

Autofill google forms based on user input

Alright stack friends,
I'm working on my first projects using google scripts and it's been pretty fun so far. My project is to create a form for data entry that can either accept an ID number and fill in the rest of the fields, or let the user fill out the entire form. Basically my method to fill in the other fields is just to have a lookup table on the second sheet. When the user submits a form, the script runs, looks for the ID of the last row, scans the reference table for the ID, and then fills in the details.
I think the problem I'm having is the assumption that the data from the form is already in the sheet when the script runs. The problem I noticed is that the script sometimes fails to fill in the gaps. I tried creating form submissions in a loop with the same ID and they function somewhat erratically but it seems like the last sumbission always works which would make sense if the script executions are not matching up with the form submissions. Here's the script for reference:
function fillGaps() {
// First take in the appropriate spreadsheet objects and get the sheets from it
var ss = SpreadsheetApp.openById(id);
var sheet = ss.getSheets()[0];
var refSheet = ss.getSheets()[1];
// Here's the last rows' index
var lastRow = sheet.getLastRow();
var lastRowRef = refSheet.getLastRow();
// now this is an array of values for the last row and the student ID entered
var response = sheet.getRange(lastRow, 1, 1, 7).getValues();
var enteredID = response[0][1];
// Next we're going to try to load up the lookup table and scan for the ID
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()).getValues();
var row = 0;
while(enteredID != stuIDs[row] && row <= lastRowRef){
row++;
}
// Okay at this point the row variable is actually -2 from what the sheet index
// is that I'm thinking of. This is because we didn't load the first row (names)
// and the way arrays are indexed starts with 0.
row++;
row++;
// now assuming that it found a match we'll fill in the values
if(row < refSheet.getLastRow()){
// Alright now we need to wrangle that row and format the data
var matchedRow = refSheet.getRange(row, 1, 1, 6).getValues();
// modify the response
var replacement = [response[0][0],enteredID, matchedRow[0][1],matchedRow[0][0],matchedRow[0][2],matchedRow[0][4],matchedRow[0][5]];
sheet.getRange(lastRow, 1, 1, 7).setValues([replacement]) ;
}
}
So I'm wondering:
Does this seem like the right diagnosis?
If so, what would be the best way to remedy? I thought of adding a little delay into the script as well as trying to capture the submissions timestamp (not sure how to do that)
Thank you much!
The following code gives a 2D array:
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()).getValues();
Also,refSheet.getLastRow gives the last row, lets say it is 10 in this case. The syntax for getRange is getRange(row, column, numRows) and the last argument is the number of rows, not the last column. So in the above code the selected range would be row 2 - 11 rather than 2- 10. Unless that is what you intended, modify the code like so:
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()-1).getValues();
To access the values in stuIDs you should use stuIDs[row][0] (2D array) to check for matching ID. Assuming your ID was to be matched was in column 1.
Secondly, in the loop you are using the following to check for the last index in array row <= lastRowRef which will cause it go out of range(because array starts at 0 and sheet row at 1) instead use this row < stuIDs.length
Finally, in case you don't find a match you will end up with the last row and your code will end you taking the last row as the matched index. This can be prevented by using a boolean variable to check for a match.
var foundId = false
var row = 0;
var i = 0;
for (i in stuIDs){
if(stuIDs[i][0] == enteredID)
foundID = true
break
}
}
row = i + 2
if (foundID){
var matchedRow = refSheet.getRange(row, 1, 1, 6).getValues();
// modify the response
var replacement = [response[0][0],enteredID, matchedRow[0][1],matchedRow[0][0],matchedRow[0][2],matchedRow[0][4],matchedRow[0][5]];
sheet.getRange(lastRow, 1, 1, 7).setValues([replacement]) ;
}
PS: You can also use event objects to get the values of response (eventObj.values). As mentioned here: https://developers.google.com/apps-script/guides/triggers/events
Hope that helps!