Using for and if loops in Google Apps Script - 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'.

Related

Protect range (not entire rows, but multi height and partial width) based on another cell value

NOTE: my question is NOT answered by Protecting/unprotecting range based on another cell value
My question asks about a range, with only part of the rows selected, NOT entire rows as in that question. I have extensively searched the site and there isn't anything that answers my question.
Please read my query properly prior to linking it to something else that does not answer it.
QUESTION
I have created a shared test Google Sheets workbook (TestWBook) to illustrate what I am trying to do. The 'Outgoing' sheet is where I need the restrictions to be applied; it is used to store order data.
The whole 'Outgoing' sheet (A1:AS), except range A2:M882, is currently protected so that only the owner and two other editors can make changes to it (let's call them editor1#email.com and editor2#email.com).
Other editors enter data in A2:M882 to specify new order requirements (which is why this range started by not being protected).
I now need to protect each row between columns A and M(active_row) from all editors but the owner and two other named editors (editor1#email.com and editor2#email.com) whenever a date is entered in column O (Date Sent) (or if that cell is not blank, either would work).
I don't want to protect an entire single row every time the script runs, as I don't want to end up with hundreds of protected ranges.
I want the protection to be applied dynamically to the range from A2 to M(active_row), so that effectively that one protected range is updated every time a date/value is entered in column O.
I have been looking through scripts for the last two days and cannot find a way to specify this dynamic range (sorry, I can adapt scripts but cannot write them up from scratch!)
Can anyone help?
Try
function onEdit(event) {
var sh = event.source.getActiveSheet();
var rng = event.source.getActiveRange();
if (sh.getName() != 'Outgoing') return;
if (rng.getColumn() != 15) return;
// save the protection parameters
var p1 = sh.getProtections(SpreadsheetApp.ProtectionType.SHEET)[0];
// delete actual protection
let protection = sh.getProtections(SpreadsheetApp.ProtectionType.SHEET)[0];
if (protection) { if (protection.canEdit()) { protection.remove(); } }
// rebuild protection
var p2 = sh.protect();
p2.setDescription(p1.getDescription());
p2.setWarningOnly(p1.isWarningOnly());
if (!p1.isWarningOnly()) {
p2.removeEditors(p2.getEditors());
p2.addEditors(p1.getEditors());
}
// define new uprotected area
p2.setUnprotectedRanges([sh.getRange('A' + rng.getRow() + ':M882')]);
}

Trying to generate a unique code for every form reply

I am having some trouble getting a script that can generate a unique code for every form reply entry to work. Now, when I first tried writing it, I used the function name onFormReply(e) since I had read it somewhere, but turn out it didn't really work, so I'm trying to use onEdit(e), but it marks most values of the variables I wrote as undefined, even the argument (e) of the function itself (which is theoretically suposed to be custom and made to resemble the cell/s where the edit took place). Here is the code:
function onEdit(e) {
const ss = SpreadsheetApp.getActiveSheet();
const row = e.getRow();
var date = ss.getRange(row,1).getValue();
var department = ss.getRange(row,6).getValue();
department = department[0] + department[1]
var uniqueNumber = ss.getLastRow()
var finalCode = department + finalDate + uniqueNumber
ss.getRange(row,15).setValue(finalCode)
}
If you are dealing with Google Form responses, you can use the timestamp in column A as a unique ID. These timestamps are accurate down to the millisecond and will for all practical purposes always be unique.
The great benefit of this is that you do not need a script to get those unique IDs. Instead, use this array formula in row 1 of a free column in the right in the form responses sheet:
=arrayformula(
{
"Unique ID";
iferror( text( 1 / A2:A ^ -1, "yyyy-MM-dd_HH-mm-ss-000") )
}
)
The formula will fill the whole column automatically and will continue giving more results as new form responses are submitted.

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('');
}
}
}

I have a pseudocode, but can't code yet

thank you and sorry for my incredibly unexperienced question in advance. So, I want to make a code and I know what I want it to do, I just don't know how to program. What I need is:
function GenPre()
1.- delete range Presupuesto!A12:C42
2.- copy range Imp!A2:Imp!C33 VALUES in Presupuesto!A12:Presupuesto!C42 (Imp cells are formulas, and I want to copy just the values)
3.- show only used rows in column A in Presupuesto!A12:A42 (consider some rows will be already hidden, so unhiding them first would be an idea)
4.- go to sheet Presupuesto (once I do this function, I want to end up on the sheet Presupuesto
end Generar
This function will be runned by a button in another sheet in the same spreadsheet.
and so far, I have this:
function GenPre() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetbyname(Presupuesto);
//next step is to select and delete the content of the range on the sheet
}
I know I'm asking for much, I just can't find much about selecting defined cells... and I really don't know how to program yet.
Thanks a bunch!!
Edit
So, I started tweaking with what k4k4sh1 answered and got this (AND reading other posts on hiding rows containing "x" on a given cell):
function GenPre() {
var sheetp = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Presupuesto') //name a variable to the sheet where we're pasting information
var sheetc = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Imp') //name a variable to the sheet frome where we're copying information
sheetp.getRange('a12:c41').clearContent() //delete all values in the range where we're copying
sheetc.getRange('A2:C31').copyValuesToRange(sheetp,1,3,12,41); //copy from source range to destination range
sheetp.showRows(12,41); //make sure all rows in the destination range are shown
for( i=12 ; i<=41 ; i++) {
if (sheetp.getRange('A'+i).getValue() == '') { // status == ''
sheetp.hideRows(i);
}
}
}
Te script is running how it should, but now, I want it to run faster (takes 12 seconds to run, when it doesn't really look that heavy), and is there a function to switch my view to sheetp? thank you all!
You're asking us to do all the work :)
Let's start from your piece of code:
the method .getSheetByName(shName) accepts a string as argument, so you should change it to
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Presupuesto');.
Mind that JavaScript is case-sensitive, so .getSheetbyname is not the same as .getSheetByName().
According to Sheet Class Reference use sheet.getRange() to get your Range Object. Take a look to Range Class Reference: to clear the range content including formats use .clear(), to clear just the content leaving the formatting intact use .clearContent().
To hide unused rows try:
function hideRows(sheetName, column) {
var s = SpreadsheetApp.getActive().getSheetByName(sheetName);
s.showRows(1, s.getMaxRows());
s.getRange(column)
.getValues()
.forEach(function (r, i) {
if (r[0] == '') {s.hideRows(i + 1);}
});
}
// hideRows('Presupuesto', 'A12:A42');

Script to add rows, shift data and add characters for Google Docs Spreadsheet

I am running a project in Google Docs and I need help with a script. I need one that will perform the following tasks in a two column spreadsheet.
Insert a new blank row between every existing row of data.
Shift the data in the second column down one place so that they occupy the newly inserted blank row
Add curly brackets {} to the text in the data of the second column ie anytext --> {anytext}
Here is an example of how it is supposed to work:Sample GDocs spreadsheet
Your question was not far from the previous one that I just tried to answer so I though I could continue and suggest a working solution to this (easy) problem...
so there it is, not the most elegant but easy and working.
function insertBlank() {
var sh = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
var last = lastRowinCol(sh,'A');
var colA = sh.getRange('A1:A'+last).getValues();
var colB = sh.getRange('B1:B'+last).getValues();
var nA = [];
var nB = [];
for(n=0;n<last;++n){
nA.push([colA[n]])
nA.push(['']);
nB.push([''])
nB.push(['{'+colB[n][0]+'}']);
}
Logger.log(nA.length+' = '+nA)
Logger.log(nB.length+' = '+nB)
sh.getRange(1,1,nA.length,1).setValues(nA);
sh.getRange(1,2,nB.length,1).setValues(nB);
}
function lastRowinCol(sh,col){
var coldata = sh.getRange(col+'1:'+col).getValues();
for(var c in coldata){if(coldata[c][0]==''){break}}
return c
}
That said, you should preferably ask more appropriate questions and show that you have tried before coming on sto make your shopping for free ;-)
EDIT : following a very pertinent comment from Mogsdad on this other post I suggest you replace the for-next loop in function lastRowinCol with his code that iterates backwards so that it handles empty cells in the column. Moreover his code has an interesting construction since the loop limits and the condition are in the same statement.
function lastRowinCol(sh,col){
var coldata = sh.getRange(col+'1:'+col).getValues();
for (var c=coldata.length; (c) && coldata[c-1][0]==''; c--) {}
return c
}