Related
I have a spreadsheet that I owner and I have 10 users with editor permission
as there are quite a lot of cells to unlock or lock, it might be best to lock everything first
and unlock the ones that they can safely use without accidentally editing a formula incorrectly
now I'm just guessing what the best solution would be
I would like to avoid that users is deleted
if it matters then no one is in a group
these would be the ones that the editors can edit
['B3:U27', 'W3:AP27', 'E29:E31', 'I29:I31', 'M29:M31', 'Q29:Q31', 'U29:U31', 'Z29:Z31', 'AD29:AD31', 'AH29:AH31', 'AL29:AL31', 'AP29:AP31', 'B29', 'F29', 'J29', 'N29', 'R29', 'W29', 'AA29', 'AE29', 'AI29', 'AM29', 'C29', 'G29', 'K29', 'O29', 'S29', 'X29', 'AB29', 'AF29', 'AJ29', 'AN29', 'D29', 'H29', 'L29', 'P29', 'T29', 'Y29', 'AC29', 'AG29', 'AK29', 'AO29', 'B31', 'F31', 'J31', 'N31', 'R31', 'W31', 'AA31', 'AE31', 'AI31', 'AM31', 'C31', 'G31', 'K31', 'O31', 'S31', 'X31', 'AB31', 'AF31', 'AJ31', 'AN31', 'D31', 'H31', 'L31', 'P31', 'T31', 'Y31', 'AC31', 'AG31', 'AK31', 'AO31', 'B33:C33', 'F33:G33', 'J33:K33', 'N33:O33', 'R33:S33', 'W33:X33', 'AA33:AB33', 'AE33:AF33', 'AI33:AJ33', 'AM33:AN33' ,'D33:E33', 'H33:I33', 'L33:M33', 'P33:Q33', 'T33:U33', 'Y33:Z33', 'AC33:AD33', 'AG33:AH33', 'AK33:AL33', 'AO33:AP33'];
everything else can be locked
will 1 or 2 extra sheets be created each day and will this apply to all sheets from now on?
does it matter that already have a few sheets with manually protection (without a script)?
users will be notified of this
or is it all just happening in the background?
UPDATE:
I found the right script to protect the page and unlock the range
and it works perfectly
link
function testProtect() {
var sheet = SpreadsheetApp.getActiveSheet();
var protection = sheet.protect().setDescription('Sample protected sheet');
var unprotected = sheet.getRange('B3:I27');
protection.setUnprotectedRanges([unprotected]);
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
but there were two things I couldn't figure out
how can I apply it to all sheets and add more cells and ranges?
because if I add another range it gives an error
var unprotected = sheet.getRange('B3:I27','F29:I29');
Exception: B3:I27 cannot be converted to int type (line 4 in the "Code" file)
thanks in advance for any help!
If I understand your post correctly, here's your goal:
Create a script to lock your sheet and only allow specific ranges to be editable for the users with edit access.
Apply that script to all of your sheets on your spreadsheet file.
Recommended Solution:
You can refer to this sample script below where it locks your sheet and only unlock specific ranges you setup.
Sample script
[UPDATED]
function main(){ //Main function to run
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var disregard = ["Sheet3","Sheet4","Sheet5"]; //ADD SHEET NAMES HERE THAT YOU WANT TO BE DISREGARDED
for(var x=0; x<sheets.length; x++){
if(disregard.some(data => sheets[x].getName().includes(data))){
//E.g. Disregard any sheet names added on the "disregard" array
}else{
unlockCertainRanges(sheets[x]);
}
}
}
function unlockCertainRanges(currentSheet){ //Function to unlock certain ranges on your spreadshseet
var sheet = currentSheet;
// Remove all range protections in the spreadsheet
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.remove();
}
var protection = sheet.protect();
//restrict editors to owner
protection.getRange().getA1Notation();
var eds = protection.getEditors();
protection.removeEditors(eds);
//set unprotected ranges
var ranges = protection.getUnprotectedRanges();
var data = ["A1:A5","B6:B10","C11:C15"]; // ADD YOUR RANGES HERE
data.forEach(res => { //LOOPS INTO EVERY ARRAY CONTAINING SPECIFIC RANGES
ranges.push(sheet.getRange(res));
protection.setUnprotectedRanges(ranges); //REMOVES THE PROTECTION ON THE RANGE
});
}
Note:
Borrowed a snippet of script to unlock specific ranges from How to protect a sheet then unprotect specific cells as reference.
Result:
Sample Sheet
All cells are locked except the ranges "A1:A5","B6:B10" & "C11:C15" (contains the "Unlock" word for visibility)
Other cells are locked
Unlocked range cells are editable
I have a spreadsheet that I owner and I have 10 users with editor permission
as there are quite a lot of cells to unlock or lock, it might be best to lock everything first
and unlock the ones that they can safely use without accidentally editing a formula incorrectly
now I'm just guessing what the best solution would be
I would like to avoid that users is deleted
if it matters then no one is in a group
these would be the ones that the editors can edit
['B3:U27', 'W3:AP27', 'E29:E31', 'I29:I31', 'M29:M31', 'Q29:Q31', 'U29:U31', 'Z29:Z31', 'AD29:AD31', 'AH29:AH31', 'AL29:AL31', 'AP29:AP31', 'B29', 'F29', 'J29', 'N29', 'R29', 'W29', 'AA29', 'AE29', 'AI29', 'AM29', 'C29', 'G29', 'K29', 'O29', 'S29', 'X29', 'AB29', 'AF29', 'AJ29', 'AN29', 'D29', 'H29', 'L29', 'P29', 'T29', 'Y29', 'AC29', 'AG29', 'AK29', 'AO29', 'B31', 'F31', 'J31', 'N31', 'R31', 'W31', 'AA31', 'AE31', 'AI31', 'AM31', 'C31', 'G31', 'K31', 'O31', 'S31', 'X31', 'AB31', 'AF31', 'AJ31', 'AN31', 'D31', 'H31', 'L31', 'P31', 'T31', 'Y31', 'AC31', 'AG31', 'AK31', 'AO31', 'B33:C33', 'F33:G33', 'J33:K33', 'N33:O33', 'R33:S33', 'W33:X33', 'AA33:AB33', 'AE33:AF33', 'AI33:AJ33', 'AM33:AN33' ,'D33:E33', 'H33:I33', 'L33:M33', 'P33:Q33', 'T33:U33', 'Y33:Z33', 'AC33:AD33', 'AG33:AH33', 'AK33:AL33', 'AO33:AP33'];
everything else can be locked
will 1 or 2 extra sheets be created each day and will this apply to all sheets from now on?
does it matter that already have a few sheets with manually protection (without a script)?
users will be notified of this
or is it all just happening in the background?
UPDATE:
I found the right script to protect the page and unlock the range
and it works perfectly
link
function testProtect() {
var sheet = SpreadsheetApp.getActiveSheet();
var protection = sheet.protect().setDescription('Sample protected sheet');
var unprotected = sheet.getRange('B3:I27');
protection.setUnprotectedRanges([unprotected]);
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
but there were two things I couldn't figure out
how can I apply it to all sheets and add more cells and ranges?
because if I add another range it gives an error
var unprotected = sheet.getRange('B3:I27','F29:I29');
Exception: B3:I27 cannot be converted to int type (line 4 in the "Code" file)
thanks in advance for any help!
If I understand your post correctly, here's your goal:
Create a script to lock your sheet and only allow specific ranges to be editable for the users with edit access.
Apply that script to all of your sheets on your spreadsheet file.
Recommended Solution:
You can refer to this sample script below where it locks your sheet and only unlock specific ranges you setup.
Sample script
[UPDATED]
function main(){ //Main function to run
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var disregard = ["Sheet3","Sheet4","Sheet5"]; //ADD SHEET NAMES HERE THAT YOU WANT TO BE DISREGARDED
for(var x=0; x<sheets.length; x++){
if(disregard.some(data => sheets[x].getName().includes(data))){
//E.g. Disregard any sheet names added on the "disregard" array
}else{
unlockCertainRanges(sheets[x]);
}
}
}
function unlockCertainRanges(currentSheet){ //Function to unlock certain ranges on your spreadshseet
var sheet = currentSheet;
// Remove all range protections in the spreadsheet
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.remove();
}
var protection = sheet.protect();
//restrict editors to owner
protection.getRange().getA1Notation();
var eds = protection.getEditors();
protection.removeEditors(eds);
//set unprotected ranges
var ranges = protection.getUnprotectedRanges();
var data = ["A1:A5","B6:B10","C11:C15"]; // ADD YOUR RANGES HERE
data.forEach(res => { //LOOPS INTO EVERY ARRAY CONTAINING SPECIFIC RANGES
ranges.push(sheet.getRange(res));
protection.setUnprotectedRanges(ranges); //REMOVES THE PROTECTION ON THE RANGE
});
}
Note:
Borrowed a snippet of script to unlock specific ranges from How to protect a sheet then unprotect specific cells as reference.
Result:
Sample Sheet
All cells are locked except the ranges "A1:A5","B6:B10" & "C11:C15" (contains the "Unlock" word for visibility)
Other cells are locked
Unlocked range cells are editable
I have a problem where I have two sheets. one sheet is the source spreadsheet and another is a target spreadsheet. The source spreadsheet has a source sheet has which is the master database and the target spreadsheet has the target where we want to fetch data from source sheet based on emails provided in the Emails tab in the target spreadsheet.
I want the following things to happen with a script and not with IMPORTRANGE or QUERY:
The target spreadsheet will have multiple copies so I want to connect the target spreadsheet with the source spreadsheet based on the source spreadsheet's id.
I want the email matches to be case insensitive so that the users of the target spreadsheet can type emails in any case.
The Emails can go up to 50 or let's say get the last row for that column.
It will be great if the script shows a pop up saying updated after it has fetched the data.
The source sheet might have data up to 15000 rows so I am thinking about speed too.
I have shared both of the spreadsheets with hyperlinks to their names. I am not really great at scripts so it will be helpful if you can leave comments in it wherever you feel like. I would truly appreciate your help.
Thanks in advance!
Script here:
function fetch() {
//get the sheets
var source_Ssheet = SpreadsheetApp.openById('19FkL3rsh5sxdujb6x00BUPvXEEhiXfAeURTeQi3YWzo');
var target_Ssheet = SpreadsheetApp.getActiveSpreadsheet();
//get the tabs
var email_sheet = target_Ssheet.getSheetByName("Emails");
var target_sheet = target_Ssheet.getSheetByName("Target Sheet");
var source_sheet = source_Ssheet.getSheetByName("Source Sheet");
//get ranges
var email_list = email_sheet.getRange("B2:B");
var target_sheet_range = target_sheet.getRange("A1:F100");
var source_sheet_range = source_sheet.getRange("A1:F100");
//get last rows
var last_email_name = email_list.getLastRow();
var last_target_sheet_range = target_sheet_range.getLastRow();
var last_source_sheet_range = source_sheet_range.getLastRow();
//start searching for emails
for (var i=3; i < last_email_name.length+1; i++)
{
for(varj=3; j< last_source_sheet_range.length+1; j++ )
{
if(source_sheet_range[j][3].getValue() == email_list[i][3].getValue())
{
//copy matches to target sheet
target_sheet.getRange((last_target_sheet_range + 1),1,1,10).setValues(master_sheet_range[j].getValues());
}
}
}
}
Several things
last_email_name and last_source_sheet_range are numbers - they do not have any length, this is why your first forloops are not working
You are missing a space in varj=3;
email_list[i][3].getValue() does not exist because email_list only includes B - that only one column. I assume you meant email_list[i][0].getValue()
ranges cannot be addressed with the indices [][], you need to retrieve the values first to have a 2D value range.
You email values in the different sheets do not follow the same case. Apps Script is case sensitive, to suee the == comparison you need to use the toLowerCase() method.
Also mind that defining getRange("B2:B") will include many empty rows that you don't need and will make your code very slow. Replace it through getRange("B2:B" + email_sheet.getLastRow());
Have a look here at the debugged code - keep in mind that there is still much room for improvement.
function fetch() {
//get the sheets
var source_Ssheet = SpreadsheetApp.openById('19FkL3rsh5sxdujb6x00BUPvXEEhiXfAeURTeQi3YWzo');
var target_Ssheet = SpreadsheetApp.getActiveSpreadsheet();
//get the tabs
var email_sheet = target_Ssheet.getSheetByName("Emails");
var target_sheet = target_Ssheet.getSheetByName("Target Sheet");
var source_sheet = source_Ssheet.getSheetByName("Source Sheet");
//get ranges
var email_list = email_sheet.getRange("B2:B" + email_sheet.getLastRow()).getValues();
var target_sheet_range = target_sheet.getRange("A1:F100").getValues();
var source_sheet_range = source_sheet.getRange("A1:F100").getValues();
var last_target_sheet_range = target_sheet.getLastRow();
//start searching for emails
for (var i=1; i < email_list.length; i++)
{
for(var j=1; j< source_sheet_range.length; j++ )
{
if(source_sheet_range[j][0].toLowerCase() == email_list[i][0].toLowerCase())
{
target_sheet.getRange((last_target_sheet_range + 1),1,1,6).setValues([source_sheet_range[j]]);
}
}
}
}
I have searched high and low but I have been unable to find an answer (I am sure I am not explaining it right)
I have a Google Sheet that have multiple sheets (tabs) labeled TabA, TabB and TabC.
On this Google Sheet, I submit a slash command on Slack, which then auto-fills a row on one of the tabs using apps script.
What I am trying to do is simply insert a word called TabA into a specific cell each time a new row has been detected. And insert a word called TabB when a new row has been made on TabB sheet etc.
I am sure I just am typing my questions wrong which is why I am unable to find an answer.
I am not actually sure which part of the code posts to the sheet, I think it is this?
if(sheetName) {
sheetName = sheetName.charAt(0).toUpperCase() + sheetName.slice(1)
} else {
sheetName = "Current"
}
// Find sheet
var sheetFlag = false;
for (var r = 1; r < settings.length; r++) {
if (settings[r][1] == channelID) {
var sheetID = settings[r][2];
var targetChannelID = settings[r][4];
var title = settings[r][0];
sheetFlag = true;
break;
}
}
if (sheetFlag) {
var sheet = SpreadsheetApp.openById(sheetID).getSheetByName(sheetName);
if(!sheet) {
sheet = SpreadsheetApp.openById(sheetID).insertSheet(sheetName);
}
var lastRow = sheet.getLastRow();
var slackDetails = ["", "", text1, "","","","","",realName, new Date(),title,text2];
// paste the slack details to the sheet
sheet.getRange(lastRow + 1,1,1,slackDetails.length).setValues([slackDetails]);```
Thank you in advance
If I understood you correctly, you want to:
Keep track of new rows that are added to each sheet in your spreadsheet (TabA, TabB, TabC).
Write the name of the sheet in successive rows of column D of each sheet every time news rows are detected.
As you were told in the comments, Apps Script has no triggers to track changes made to the spreadsheet by a script. For example, onEdit trigger "runs automatically when a user changes the value of any cell in a spreadsheet".
Workaround (time-based trigger and script properties):
A possible workaround to this limitation is using a time-based trigger that will fire a function periodically. You can create this trigger manually, or programmatically, by running this function once:
function createTrigger() {
ScriptApp.newTrigger("trackRows")
.timeBased()
.everyMinutes(1)
.create();
}
This will fire the function trackRows every minute. This function's purpose is to track changes to each sheet rows since last time it was fired (in this example, 1 minute ago) and write the sheet name to a certain cell if the sheet has more rows with content than during last execution.
To accomplish this, you can use the Properties Service, which lets you store key-value pairs and use them in later executions.
The function trackRows could be something along the following lines:
function trackRows() {
var props = PropertiesService.getScriptProperties();
var ss = SpreadsheetApp.openById("your-spreadsheet-id"); // Please change accordingly
var sheets = ss.getSheets();
sheets.forEach(function(sheet) {
var sheetName = sheet.getName();
var currentRows = sheet.getLastRow();
var oldRows = props.getProperty(sheetName);
if (currentRows > oldRows) {
var firstRow = 2;
var column = 4;
var numRows = sheet.getLastRow() - firstRow + 1;
var rowIndex = sheet.getRange(firstRow, column, numRows).getValues().filter(function(value) {
return value[0] !== "";
}).length;
var cell = sheet.getRange(rowIndex + firstRow, column);
cell.setValue(sheetName);
}
props.setProperty(sheetName, currentRows);
});
}
This function does the following:
Retrieve the script properties that were stored in previous executions.
Get all the sheets in the spreadsheet.
Check the last row with content in each sheet (via Sheet.getLastRow()), and compare the value with the one previously stored in script properties.
If the current last row is higher than the one stored in properties, write the sheet name in the first empty row of column D of the corresponding (starting at D2).
Store the current last row in script properties.
Notes:
The script is adding the sheet name to the first empty row of column D once, if it detects that new rows were added. It's not taking into account how many rows were added since last execution, it only considers if rows were added. This could easily be changed though, if that's what you wanted.
If you want to start from fresh, it would be useful to delete all previously stored properties. To do that, you could run this function once:
function deleteProps() {
var props = PropertiesService.getScriptProperties();
props.deleteAllProperties();
}
Reference:
Class ClockTriggerBuilder
Class PropertiesService
Sheet.getLastRow()
I have a bunch of data I want to put in to multiple sheets, and to do it manually would take time and I would like to learn scripting too.
So say I have a sheet with the states in one column.
I would like to have a script make new sheets based off the values of that column, and make a hyperlink to those sheets, and sort the sheets alphabetically.
In each sheet, I need to have the A1 cell the same name as the sheet.
Here is an example of states
Any suggestions would be helpful
Edit:
This is code that can make sheets based on the values of the columns.
function makeTabs() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var last = sheet.getLastRow();//identifies the last active row on the sheet
//loop through the code until each row creates a tab.
for(var i=0; i<last; i++){
var tabName = sheet.getRange(i+2,1).getValue();//get the range in column A and get the value.
var create = ss.insertSheet(tabName);//create a new sheet with the value
}
}
(note the "sheet.getRange(i+2,1" assumes a header, so pulls from the first column, starting on the second row)
I still need to:
Add a hyper link in the index sheet to the State's sheet: example: A2 on the Index sheet
would be =HYPERLINK("#gid=738389498","Alabama")
Also I need the A1 cell of the State's page to have the same info as
the index. example: Alabama's A1 cell would be =Index!A2
You could take a look at this script:
function createSheets(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var indexSheet = ss.getSheetByName('Index');
var indexSheetRange = indexSheet.getDataRange();
var values = indexSheetRange.getValues();
var templateSheet = ss.getSheetByName('TEMPLATE_the_state');
templateSheet.activate();
var sheetIds = [],
state,
sheetId,
links = [];
for (var i = 1 ; i < values.length ; i++){
state = values[i][0];
sheetId = undefined;
try{
var sheet = ss.insertSheet(state, {template: templateSheet});
SpreadsheetApp.flush();
sheet.getRange("A1:B1").setValues([['=hyperlink("#gid=0&range=A' +(i+1)+'","go back to index")',state]]);
sheetId = sheet.getSheetId();
}
catch (e) { Logger.log('Sheet %s already exists ' , sheet)}
sheetIds.push([sheetId,state]);
}
sheetIds.forEach(function(x){
links.push(['=HYPERLINK("#gid='+x[0]+'&range=A1","'+x[1]+'")']);
});
indexSheet.getRange(2,1,links.length,links[0].length).setValues(links) // in this case it is clear to us from the outset that links[0].length is 1, so we could have written 1
}
Note that in my version, I created a template sheet from which to base all the state sheets from. This wasn't what you asked for, but I wanted to see what it would do.
The resulting sheet is here: https://docs.google.com/spreadsheets/d/1Rk00eXPzkfov5e3D3AKOVQA2UdvE5b8roG3-WeI4znE/edit?usp=sharing
Indeed, I was surprised at how long it took to create the full sheet with all the states - more than 250 secs. I looked at the execution log, which I have added to the sheet in its own tab. There it is plain to see that the code is quick, but sometimes (why only sometimes, I don't know) adding a new tab to the spreadsheet and/or flushing the formulas on the spreadsheet is very slow. I don't know how to speed it up. Any suggestions welcome. (I could try the Google Sheets API v4, probably would be much faster ... but that is much more work and tougher to do.)