Google New Sheets Protection Issues - google-apps-script

I am experiencing some strange problems with protection in Google New Sheets. I have created a demo of what I am experiencing. The URL to the sheet is:
https://docs.google.com/spreadsheets/d/1IbAiqU6oN48Ql_wM3TeRl9TqG6DFsBKtc86jElv0Kbo/edit?usp=sharing
I have protected the sheet for edit by owner only except for rows 5 to 7 using the 'Sheet protect except certain cells' method under 'Data - Protected sheets and ranges...'
I also have a simple User Function menu which is invoked on open wich contains a simple Google Apps Script to insert a given number of rows (code below).
The following is happening when another user accesses the sheet:
The basic protection seems to be working. The user can only edit the rows 5 to 7.
The insert row function (selected under User Functions menu) produces a 'Service error: Spreadsheets'.
If the user tries to delete any of the 3 unprotected rows then the message 'Can't save your changes. Copy any recent changes, then revert to an earlier version...' appears in a red box at the top of the screen. Clicking on the 'revert to an earlier version...' link reverses the delete.
If I remove all protection then everything is 100% for the user - insert rows funtion - delete rows etc.
The functionality I have reproduced here is very similar to what I have been using in the old sheets for years without any problems (i.e. protecting certain areas of the spreadsheet from edit by shared users).
I must add, I posted the issue about the insert row function not working a couple of days ago.
Here's my function code:
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [{name: "Insert Rows", functionName: "insertRows"}];
ss.addMenu("User Functions", menuEntries);
}
function insertRows() {
var numRows = Browser.inputBox('Insert Rows', 'Enter the number of rows to insert', Browser.Buttons.OK);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var curs = sheet.getActiveCell();
var cursRow = curs.getRow();
var cursLastCol = sheet.getLastColumn();
sheet.insertRowsAfter(cursRow, numRows);
var source_range = sheet.getRange(cursRow,1,1,cursLastCol);
var target_range = sheet.getRange(cursRow+1,1,numRows);
source_range.copyTo(target_range);
Browser.msgBox('Insert Rows', +numRows+' rows successfully inserted.', Browser.Buttons.OK);
}
Can anyone help with this. I have some large customers I have built complex online spreadsheets for that now don't function correctly under New Sheets.

Try setting up a project trigger for onOpen() rather than using the simple onOpen(). Then it runs with your privileges rather than those of the current user. Just click on Resources/Current Project Triggers and add a new trigger.

Related

Script that adds new row to sheets in selected AND another Google Spreadsheet

I'm working on a Spreadsheet to keep track of team member's project hours. I've created a Spreadsheet per team member for them to fill out weekly, and a project overview Spreadsheet that takes in all data through IMPORTRANGE.
To be able to quickly add a new project I want a macro to insert a new row in the Project overview + the separate Spreadsheets per team member. However I can't figure out how to write the correct code for the separate team member Spreadsheets. What's going wrong here?
If possible I'd also like to make a macro to DELETE a row in the project overview + team member spreadsheets, and one to HIDE a row...
Project overview
Team member Kate
Team member David
My current code:
function InsertRow() {
var ss = SpreadsheetApp.getActive();
var allsheets = ss.getSheets();
var row = SpreadsheetApp.getActiveRange().getRow();
// Array holding the names of the sheets to exclude from the execution
// I only managed to make it work when I exclude the sheet that I actually want to affect instead of the other way around?
var exclude = (["PROJECTS"] ||
SpreadsheetApp.openById("1xjR3lx5_KAA9nqiD3YsjZnulQaMyWGPQqgYsjtzQ0xI").getSheets() ||
SpreadsheetApp.openById("1Q5gtZlqf41of1Zwi8pvZbDx4NN5LcDh5SxfwasLUDMU").getSheets())
for(var s in allsheets){
var sheet = allsheets[s];
// Stop iteration execution if the condition is meet.
if(exclude.indexOf(sheet.getName())==-1) continue;
sheet.insertRowBefore(row);
}
}
As I see it you have a couple of options, which I'll be listing here as A, B, and C. Please note that you might need two different .GS files as you are linking to two sheets
A
Try code found on google app script documentation
I found the google apps script documentation for this command found here, so you might want to check that for this questions and others , but here is the exact code included
// The code below opens a spreadsheet using its ID and logs the name for it.
// Note that the spreadsheet is NOT physically opened on the client side.
// It is opened on the server only (for modification by the script).
var ss = SpreadsheetApp.openById("abc1234567");
Logger.log(ss.getName());
B
use open by url instead of open by id
Your issue might be that your current id isn't correct, I have no way of knowing, so here is some alternate code here (link to documentation here)
// The code below opens a spreadsheet using its id and logs the name for it.
// Note that the spreadsheet is NOT physically opened on the client side.
// It is opened on the server only (for modification by the script).
var ss = SpreadsheetApp.openByUrl(
'https://docs.google.com/spreadsheets/d/abc1234567/edit');
Logger.log(ss.getName());
C
Tie the google script to one sheet
This last option doesn't require any code, just an explanation. Instead of trying to link your script to two separate sheets, you might be able to automatically link it to a single google sheet and create two pages in the sheets file that you treat as two different sheets but are one thing. This might not be what you want, but I included it anyways. You link the sheet to the code automatically by:
1 opening your sheet
2 going to "tools"
3 clicking script editor
4 copy and paste your code (except for the "open by id" part)
5 success!
Your exclude variable doesn't contain what you think it does. You're using an "or" operator (||), which will take the first "truthy" value and skip the rest.
console.log((["PROJECTS"] || 'something else')); // ["PROJECTS"]
Moreover, you don't have a good way of telling which spreadsheet belongs to which team member. To solve that problem, you can create an object.
const teamSpreadsheetIds = {
'DAVID': 'ABC',
'KATE': '123',
};
console.log(teamSpreadsheetIds['DAVID']); // ABC
With the teamSpreadsheetIds object, you can now go about updating your team member sheets locally as well as their individual spreadsheets. The "PROJECTS" sheet is unique, so there's only one check for it.
function InsertRow() {
const ss = SpreadsheetApp.getActive();
const allSheets = ss.getSheets();
const row = SpreadsheetApp.getActiveRange().getRow();
const teamSpreadsheetIds = {
'DAVID': '1Q5gtZlqf41of1Zwi8pvZbDx4NN5LcDh5SxfwasLUDMU',
'KATE': '1xjR3lx5_KAA9nqiD3YsjZnulQaMyWGPQqgYsjtzQ0xI',
};
for (let sheet of allSheets) {
const sheetName = sheet.getName();
const memberSpreadsheetId = teamSpreadsheetIds[sheetName];
const isSkippable = memberSpreadsheetId === undefined && sheetName !== 'PROJECTS';
if (isSkippable) { continue };
// Insert a row in the local sheet
sheet.insertRowBefore(row);
// Get the member sheet and insert a row
if (memberSpreadsheetId) {
const memberSpreadsheet = SpreadsheetApp.openById(memberSpreadsheetId);
const memberSheet = memberSpreadsheet.getSheets()[0]; // Assumes the first sheet is the one to modify
memberSheet.insertRowBefore(row);
}
}
}

Google sheets appscript to copy tabs to new sheets

I have a google sheet with around 190 tabs on that i need to split into 190 different files
The files need to be named the same as the tab, the contents of the tab need to be copied as values but i also need to bring the formatting accross (just not the formulas).
I have looked around, and through a combination of previous questions and answers plus using the function list help have formed the following code. It actually works for the first few tabs but then throws up an error about being unable to delete the only sheet.
function copySheetsToSS() {
var ss = SpreadsheetApp.getActive();
for(var n in ss.getSheets()){
var sheet = ss.getSheets()[n];// look at every sheet in spreadsheet
var name = sheet.getName();//get name
if(name != 'master' && name != 'test'){ // exclude some names
var alreadyExist = DriveApp.getFilesByName(name);// check if already there
while(alreadyExist.hasNext()){
alreadyExist.next().setTrashed(true);// delete all files with this name
}
var copy = SpreadsheetApp.create(name);// create the copy
sheet.copyTo(copy);
copy.deleteSheet(copy.getSheets()[0]);// remove original "Sheet1"
copy.getSheets()[0].setName(name);// rename first sheet to same name as SS
var target_sheet = copy.getSheetByName(name);
var source_range = sheet.getRange("A1:M50");
var target_range = target_sheet.getRange("A1:M50");
var values = source_range.getValues();
target_range.setValues(values);
}
}
}
I am hoping someone can tell me what i have done wrong as I cannot figure it out at this point. I am also open to better solutions though please be aware I am very much a beginner on google appscript, nothing too complex please.
thankyou
In principle your script correctly adds a new sheet to the new spreadsheet before removing the preexisting one
However, mind that calls to service such as SpreadsheetApp are asynchronous.
And this becomes the more noticeable, the longer your script runs.
In your case it apparently leads to behavior that the only sheet is being deleted before the new sheet is being created.
To avoid this, you can force the execution to be synchronous by implementing calls to SpreadsheetApp.flush().
This will ensure that the old sheet won't be deleted before the new one gets inserted.
Sample:
copy.deleteSheet(copy.getSheets()[0]);// remove original "Sheet1"
SpreadsheetApp.flush();
copy.getSheets()[0].setName(name);
You might want to introduce call toflush()` also at other positions where it is important for the code to run synchronously.

Google Sheets as Database

I'm trying to enter some data on a sheet in google sheets create a button that submits data onto another sheet. The other sheet will be like my database.
I have little to no JS experience. I'm able to create the button and link it my script but after that, I'm lost. This code worked well to get data to my database sheet. The problem is that the data stays on the same row and when I run the script the old data is erased.
function transfer() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheets()[0];
var destination = ss.getSheets()[1];
var range1 = source.getRange("B2");
range2.copyValuesToRange(destination,3,3,2,2);
range2.clearContent();
var range2 = source.getRange("C2");
range2.copyValuesToRange(destination,4,4,2,2);
range2.clearContent();
var range3 = source.getRange("D2");
range3.copyValuesToRange(destination,5,5,2,2);
range3.clearContent();
var range4 = source.getRange("C2");
range4.copyValuesToRange(destination,4,4,2,2);
range4.clearContent();
}
The other issue is I don't want the cells to be empty so I tried to set an alert.
var range1 = source.getRange("A2");
range1.copyValuesToRange(destination,2,2,2,2);
if (range1 ==!"");
Browser.msgBox("Please Enter A Date");
It prompted the msg box but still copied the data over.
Last I would like range1 to be like a unique ID. So if I put a value in A2 on my source sheet then it will auto-fill the other cells.
Here's the link if that helps.
https://docs.google.com/spreadsheets/d/1Zi7Oc0f5AlxcRRoFMM1Q1VkkM1Co6Yo8yYBY1TvkQMc/edit?usp=sharing
I recommend that you spend more time with the documentation. Personally, I've never considered putting buttons directly on the spreadsheet. I'd rather use the menu or a dialog or a sidebar. Also you generally pick up a lot of performance to replace the use of getValue() with getValues() where appropriate.
But here's another way to approach the problem your working on. Perhaps it will give something to think about. Have fun. Your buttons do look nice though. Very nice work.
function onOpen(){//This will put a post button on a menu
SpreadsheetApp.getUi().createMenu('My Menu')
.addItem('Post Data', 'postData')
.addToUi();
}
function postData() {//this will append data fromm 'DataEntry' to 'DB' with a timestamp spliced into it.
var ss=SpreadsheetApp.getActive();
var sh1=ss.getSheetByName('DataEntry');
var sh2=ss.getSheetByName('DB');
var rg1=sh1.getRange(sh1.getLastRow(),1,1,sh1.getLastColumn());
var vA=rg1.getValues();
vA[0].splice(0,0,Utilities.formatDate(new Date(),Session.getScriptTimeZone(),"E MMM dd, yyyy HH:mm:ss"));
sh2.appendRow(vA[0]);
}
This is what my DateEntry tab looks like:
This is what my DB tab looks like:
The post just adds the last row in DataEntry to the row after the last row in DB.

Importing Data into a spreadsheet and reading the values with Google Script

We use a master spreadsheet containing all the information of the students. I want to create a UI to capture the marks of each student and write it to a Google Sheet from which I will generate their report cards.
I use the following code to import the data from the master list - the names gets imported correctly, but I cannot seem to pull the values? I just get "undefined"
/**
* A function that inserts a custom menu when the spreadsheet opens to generate the Report Spreadsheet.
*/
function onOpen() {
var menu = [{name: 'Capture Report Data', functionName: 'setUpProgressReport_'}];
SpreadsheetApp.getActive().addMenu('Progress Report', menu);
}
/**
* A set-up function that creates a Report Sheet based on the class selected
*/
function setUpProgressReport_() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Report 1');
var ui = SpreadsheetApp.getUi(),
response = ui.prompt(
'Enter Class',
'Please enter the class you would like to enter marks for',
ui.ButtonSet.OK_CANCEL),
selectedClass = response.getResponseText();
//Import names of learners by selected class from Master Sheet
var cell = sheet.getRange("A1");
cell.setFormula('=QUERY(IMPORTRANGE("1Dxjt6W54e7n2F8a2zlRZV0n-VtCoPZTC2oZgeMPd8mE","MasterList!A1:Z2000"),"SELECT Col1, Col2,Col4 WHERE Col4 contains ' + "'" + selectedClass + "'" + ' Order by Col2 asc")');
// Freezes the first row to be used as headings
sheet.setFrozenRows(1);
var lastRow = sheet.getLastRow();
var lastColumn = sheet.getLastColumn();
var values = SpreadsheetApp.getActiveSheet().getRange(lastRow, lastColumn).getValues();
Browser.msgBox(values[0][22]);
}
Use SpreadsheetApp.flush() to apply all pending spreadsheet changes before getting the values of cells previously modified by the script.
From https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#flush()
Spreadsheet operations are sometimes bundled together to improve
performance, such as when doing multiple calls to Range.getValue().
However, sometimes you may want to make sure that all pending changes
are made right away, for instance to show users data as a script is
executing.
Also could be helpful to include a test loop to be sure that the IMPORTRANGE task is complete. This test loop could check every certain amount of time,let say 500 millisecondes if certain change already occurred, in example, the script could get the last row before doing the import and compare it with the last row after it and doing a loop until the last is greater than the first.
An alternative is to use Utilities.sleep(milliseconds) alone. This could work but since the IMPORTRANGE execution time isn't deterministic we can not know for sure how many time is required.
I am no expert but I think I kind of figured out what the problem was... well in theory and maybe not in the correct technical details.
var ss = SpreadsheetApp.getActive() sets the current spreadsheet as the ss value, and this is without the imported data. So referencing this variable actually references the data before it was imported. By creating a seperate function and "refreshing" the var ss = SpreadsheetApp.getActive() solved the issue and I could retrieve data normally.

Pasting a value into a new cell each time another is updated?

I'm hoping to have 2 cells that update their value every week. Each time these cells get their values updated I would like them to also be printed onto another two cells on a different sheet. Each week these cells that they are printed onto moving down by one. For example Week 1, the two cells are printed in A1 and B1. Week 2 they are printed in A2 and B2, and so on.
I know how to do this in Excel but no idea how I can change that over to Google Sheets / Scripts.
Assumptions:
Your new inputs are in input!A1 and input!B1; and
Your outputs will by in output!A and output!B.
You can write a script by clicking on Tools on the menu and entering Script Editor.
Then write this function:
function writeData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var input = ss.getSheetByName("input")
.getRange(1, 1, 1, 2)
.getValues();
var outputSheet = ss.getSheetByName("output");
var numRows = outputSheet.getLastRow();
outputSheet.getRange(numRows + 1, 1)
.setValue(input[0][0]);
outputSheet.getRange(numRows + 1, 2)
.setValue(input[0][1]);
}
Next, set up a trigger. Within Script Editor, under Resources, select Current project's triggers and then set up your trigger. If you want it to run weekly, use a time-driven trigger. You can also set a spreadsheet-driven trigger to run on every edit. Save everything.
If you don't want to use automatic triggers, you can execute the writeData() function by adding an item on the menu bar of the spreadsheet:
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [];
menuEntries.push({name: "Write data", functionName: "writeData"});
ss.addMenu("Custom functions", menuEntries);
}
Save, and if you refresh the spreadsheet you'll see a new item on the menu bar.