I am creating workbooks for teachers to monitor students progress on a specific skill. We have a workbook with various templates they may need to copy the sheets/graphs they need for each child. These sheets are protected without the exception of the cells the teachers need to access in order to input student data. Right now when we use the COPY TO a new spreadsheet, we lose those protections. (Most of our K-2 teachers are not tech-savvy enough to have full access to a sheet with a graph.) There has to be a way to create some type of protection that carries over to copied files that is foolproof. Thanks to anyone who knows how to do this.
I tried using this code and it does copy my sheet and keep it protected, BUT how can I get it to stay protected when the COPY TO is used to another existing workbook?
function duplicateProtectedSheet() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
sheet = ss.getSheetByName("B - General Info");
sheet2 = sheet.copyTo(ss).setName("My Copy1");
var p = sheet.getProtections(SpreadsheetApp.ProtectionType.SHEET)[0];
var p2 = sheet2.protect();
p2.setDescription(p.getDescription());
p2.setWarningOnly(p.isWarningOnly());
if (!p.isWarningOnly()) {
p2.removeEditors(p2.getEditors());
p2.addEditors(p.getEditors());
// p2.setDomainEdit(p.canDomainEdit()); // only if using an Apps domain
}
var ranges = p.getUnprotectedRanges();
var newRanges = [];
for (var i = 0; i < ranges.length; i++) {
newRanges.push(sheet2.getRange(ranges[i].getA1Notation()));
}
p2.setUnprotectedRanges(newRanges);
}
Here is an example of my current workbook with the templates teachers will be copied to their own workbooks.
https://docs.google.com/spreadsheets/d/1JB95bdu4Qatx7uMZrQFP7GkiKBuuB47PCV8ZjyQWmHo/edit?usp=sharing
try this:
function test(){
var ss = SpreadsheetApp.getActiveSheet();
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
var newSheet = ss.copyTo(SpreadsheetApp.getActive());
var prot;
var p;
var editors;
for (var i = 0; i < protections.length; i++){
prot = protections[i];
p = newSheet.getRange(prot.getRange().getA1Notation()).protect();
p.setDescription(prot.getDescription());
editors = prot.getEditors();
for (var j = 0; j < editors.length; j++){
p.addEditor(editors[i]);
}
}
}
This code copies the sheet and stores the original's protections, then it set the same protections in the newly-created sheet. for you it would be a matter of setting ss and newSheet to the ones you intend to use.
Related
How to make a copy of a google sheet with content only.
My problem is that there is a function that already allows you to do this on the ranges of a sheet with ({contentonly: true}). But it doesn't work if there is an importrange.
Is there a way to make a copy of a sheet of values only (even if there is an importange)?
May be something like this :
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var destFolder = DriveApp.getFoldersByName("Experiments").next();
var file = DriveApp.getFileById(spreadsheet.getId()).makeCopy("Saved Copy", destFolder);
var newSpreadsheet = SpreadsheetApp.open(file);
for(var i = 0; i < spreadsheet.getNumSheets(); i++){
newSpreadsheet.getSheets()[i].getDataRange().setValues(spreadsheet.getSheets()[i].getDataRange().getValues());
}
But it is not very efficient, if there are many cells.
Maybe the performance problem is caused by calling too many Spreadsheet Service methods (i.e. Spreadsheet.getSheets() is called two times inside a loop). Try the following:
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var destFolder = DriveApp.getFoldersByName("Experiments").next();
var file = DriveApp.getFileById(spreadsheet.getId()).makeCopy("Saved Copy", destFolder);
var newSpreadsheet = SpreadsheetApp.open(file);
var sheets = newSpreadsheet.getSheets(); // Call SpreadsheetApp.getSheets() only once per script execution
for(var i = 0; i < sheets.length; i++){
sheets[i].getDataRange().setValues(sheets[i].getDataRange().getValues());
}
I have been using the following code (after some edits and help from the community here) to create a duplicate sheet from a template sheet. However, the number of sheets have increased now. Is there a way to make some edits in this code such that the newly created sheet shows up directly so I can right away edit without horizontally scrolling through to find the new sheet that was created from the script? .
Edited question for giving better clarity on the issue.
function duplicateSheetWithProtections() {
var ui = SpreadsheetApp.getUi();
var oldSheetName = ui.prompt('Sheet to Copy?');
var newSheetName = ui.prompt('New Sheet Name?');
var ss = SpreadsheetApp.getActiveSpreadsheet();
sheet = ss.getSheetByName(oldSheetName.getResponseText());
sheet2 = sheet.copyTo(ss).setName(newSheetName.getResponseText());
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var p = protections[i];
var rangeNotation = p.getRange().getA1Notation();
var p2 = sheet2.getRange(rangeNotation).protect();
p2.setDescription(p.getDescription());
p2.setWarningOnly(p.isWarningOnly());
if (!p.isWarningOnly()) {
p2.removeEditors(p2.getEditors());
p2.addEditors(p.getEditors());
// p2.setDomainEdit(p.canDomainEdit()); // only if using an Apps domain
/* Make the new sheet active */
ss.setActiveSheet(sheet);
}
}
}
Try
sheet2.moveActiveSheet(0)
this will be the simplest and most effective solution
I have a google sheet that has many protected ranges/cells. It is currently set up so that myself and two others have full edit access to the workbook/protected ranges/cells. All others accessing the sheet can then only edit certain cells. It works exactly how I expect.
However, when I use a script to copy that sheet and insert data, the protections for the other two full editors goes away. I do not remove protections in my code at all just add data to the sheet.
Any thoughts on why this would happen or how to fix it? Again, these two other users can access the sheet they just can no longer edit any cell/range.
Try this (sheetname should be the name of your sheet to copy):
function duplicateSheetWithProtections() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
sheet = ss.getSheetByName('sheetname');
sheet2 = sheet.copyTo(ss).setName('My Copy');
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var p = protections[i];
var rangeNotation = p.getRange().getA1Notation();
var p2 = sheet2.getRange(rangeNotation).protect();
p2.setDescription(p.getDescription());
p2.setWarningOnly(p.isWarningOnly());
if (!p.isWarningOnly()) {
p2.removeEditors(p2.getEditors());
p2.addEditors(p.getEditors());
// p2.setDomainEdit(p.canDomainEdit()); // only if using an Apps domain
}
}
}
I've been trying to copy only the content (no equations such as =IMPORTRANGE) of a spreadsheet into a new spreadsheet. I have found a solution however I was wondering if there was an easier way to do this?
I ended up iterating through each sheet in the original spreadsheet, getting the data range of each sheet, getting those values, copying each sheet into the new spreadsheet with .copyTo, deleting the "Sheet1" created with the new spreadsheet, then iterating through the new spreadsheet and pasting the values to each new sheet in the spreadsheet.
function copySpreadsheetContent() {
var ss1 = SpreadsheetApp.getActiveSpreadsheet();
var sheetNums = ss1.getNumSheets();
var ssNew = SpreadsheetApp.create('Test Spreadsheet').getId();
var ss2 = SpreadsheetApp.openById(ssNew);
var ss2Nums = ss2.getNumSheets();
var sheetVals = [];
for(var i = 0; i < sheetNums; i++){
var sheet = ss1.getSheets()[i];
sheetVals[i] = sheet.getDataRange().getValues();
var newSheet = sheet.copyTo(ss2);
newSheet.setName(sheet.getName());
}
var sheet1 = ss2.getSheetByName("Sheet1");
ss2.deleteSheet(sheet1);
for(var j = 0; j <= ss2Nums; j++){
var ss2Sheet = ss2.getSheets()[j];
ss2Sheet.getDataRange().setValues(sheetVals[j]);
}
}
This provides the desired results as far as I can tell, however it just seems like a really roundabout way of doing things and I was hoping someone might be able to point me to a more efficient or straightforward method.
Thanks for the help
My coworker ended up finding a better way to do this with a single for loop and the ability to send it to a folder in your drive
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var destFolder = DriveApp.getFoldersByName("Experiments").next();
var file = DriveApp.getFileById(spreadsheet.getId()).makeCopy("Saved Copy", destFolder);
var newSpreadsheet = SpreadsheetApp.open(file);
for(var i = 0; i < spreadsheet.getNumSheets(); i++){
newSpreadsheet.getSheets()[i].getDataRange().setValues(spreadsheet.getSheets()[i].getDataRange().getValues());
}
In Google Sheets I have a sheet with Form Responses, and to the right of the form columns I have columns with formulas that use form data for functions.
At the start I had the formulas extended down the rows so they worked on new form submissions, but found that new form submissions would clear the row out :(.
Instead of manually extending formulas down after each submission I installed Andrew Stillman's copyDown() script; what it does is copies down the formulas after scripts are submitted.
Now the problem I'm having is that the script works when run manually, but when I set to trigger on form submits it copies the said formula on that sheet and on all other sheets in the spreadsheet. I Do Not Want that side-effect, as it messes the whole spreadsheet up. :((
What I thought to do is edit script so it only works on the one Form Response sheet, not all sheets. But I don't know how to do that.
The name of the sheet I want it to run on is "Requests", and the gid=8.
How do I edit this script to only work for that one sheet?
To get code to run on just a particular sheet use the .getSheetByName() method. For example:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var reqSh = ss.getSheetByName('Requests');
There is another way that might be easier. You could try keeping one sheet purely for form submissions and use an arrayformula in a second sheet to copy any values from the first sheet to the same range in the second.
=ARRAYFORMULA('Requests'!A1:H) would copy columns A to H.
I had the same problem as you and this was the solution. I placed my formulas in the second sheet in the columns to the right of the range and copied them down in the normal way. The formulas referred to the copied range in the second sheet. It worked a treat.
I didn't come up with the idea myself - I'm sure it was suggested by someone on the Google spreadsheet forum. I should give you a link to the post but I just looked and I can't find it.
In your code you have (comments in code)
var sheets = ss.getSheets() [8]; // you choose sheet [8]
var cellAddresses = new Object();
for (var i=0; i<sheets.length; i++) { // but you enter a for loop that adresses every sheet in turn...
var range = sheets[i].getDataRange();
You should simply suppress this loop and use only the sheet number you want to be proceeded ...
The simplest way could be to do it like this :
var i = 8
var sheets = ss.getSheets() [i];
var cellAddresses = new Object();
var range = sheets[i].getDataRange();
...
and at the end of the loop delete the } that fitted the for loop
EDIT : the new code should be like this :
function copydown() {
setCopyDownUid();
setCopyDownSid();
logCopyDown();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets() [8];
var cellAddresses = new Object();
var i=8
// for (var i=0; i<sheets.length; i++) {
var range = sheets[i].getDataRange();
var lastRow = range.getLastRow();
var values = range.getValues();
for (var j=0; j<values.length; j++) {
for (var k=0; k<values[j].length; k++) {
var test = values[j][k].toString();
var start = test.indexOf("copydown");
if (start == 0) {
start = start+10;
var end = test.length-2;
var length = end-start;
var value = test.substr(start, length);
var col = k+1;
var nextRow = j+2;
var numRows = lastRow-(nextRow-1);
if (numRows>0) {
var destRange = sheets[i].getRange(nextRow, col, numRows, 1);
destRange.clear();
var newLastRow = sheets[i].getDataRange().getLastRow();
var newNumRows = newLastRow-(nextRow-1);
var newDestRange = sheets[i].getRange(nextRow, col, newNumRows, 1);
var cell = sheets[i].getRange(nextRow-1, col);
cell.setFormula(value);
cell.copyTo(newDestRange);
}
var cellAddress = cell.getA1Notation();
cellAddresses[cellAddress] = test;
}
}
}
Utilities.sleep(500);
resetCellValues(cellAddresses, sheets[i]);
}
//}