I would like to prevent the deletion of rows on a google sheet, but this is not possible to set it through permissions especially if it is the same user who can modify the sheet, can accidentally delete one or more rows and/or insert new ones. For inserting of a new row I have a script that works correctly which simply get the "INSERT_ROW" event onChange (), alerts the user and deletes the newly inserted row. For deletion instead,the user can delete a single row or even more rows at the same time ... and I don't know how to restore them in the same range from which they were deleted (and therefore also recover the values).
Here is the first working point:
/** RESTORE ROW OR COLUMN */
function onChange(e){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = SpreadsheetApp.getActiveRange();
if (e.changeType === 'INSERT_ROW'){
var row = range.getRow();
var lastRow = range.getLastRow();
var numRows = 1+lastRow-row;
sheet.deleteRows(row, numRows)
SpreadsheetApp.getUi().alert("Warning for Row");
}else if (e.changeType === 'INSERT_COLUMN'){
var col = range.getColumn();
var lastCol = range.getLastColumn();
var numCols = 1+lastCol-col;
sheet.deleteColumns(col, numCols);
SpreadsheetApp.getUi().alert("Warning for Column");
}
GmailApp.sendEmail(email#company.com,
'Warning:'+e.changeType+" row:"+ row,
"",
{
name :'WARNING'
});
}
There is no built-in / direct way to prevent that editors delete rows in Google Sheets while allowing them to user other features like filtering, sorting etc.
Under certain scenarios it might work to protect the sheet and setting some protections exceptions to allow some users to edit some cells.
If the above doesn't work for you, you might have to implement a way to backup the rows but that might not scalable and very reliable.
The most reliable approach might be to have a "backend database" (it could be another spreadsheet or another service i.e. BigQuery) then import the data into the "frontend" spreadsheet and push the changes to the backend database after they were validated.
Yes I have a backend database. In the sense that I have a goolge form that fills in the answer sheet. Then I have 4 more sheets which simply read the sheet values from the answers(but on which, other users can take actions). So for example in sheet2 I have something like (= 'Form Answers'! A: A) and next to it, I have a cell with a checkbox like "OPEN" / "CLOSED" . The point is that if the user in Sheet2 inserts/delete a row, the answers are misaligned from the checkboxes...so the values of the checkboxes no longer correspond to the information taken from the sheet "'Form Answers'!".I wanted to avoid having him insert/delete a row by mistake. So with the above procedure, I can do an undo of a new row ... it would have been nice to be able to restore any deleted row.
it would take something like "If you delete row3 of sheet2 then reinsert row3 of sheet 'Form Answer', exactly in the position of row 3 and not in append "(it would be the top to recover the "old value" of the checkbox that was lost due to deletion of the entire row
Related
I've heavily edited the original question I posted, as i have solved some of the issue myself. I'm now stuck on just one thing.
function payINVOICE() {
var ss = SpreadsheetApp.getActive();
var ds = SpreadsheetApp.openById("14imcEob2qIZbH6AjGYtf16MJxbnfkhQn1ae4jR-Nzq4");
var srcSheet = ss.getSheetByName("INVOICE_ENTRY");
var dstSheet = ds.getSheetByName("INVOICE_ENTRY");
var data_range = srcSheet.getRange('B4:J100');
var data_data = data_range.getValues();
var data_clean = data_data.filter(function (r) {return r[1]});
var clear_range = srcSheet.getRange('B4:I100');
var lr = dstSheet.getLastRow();
dstSheet.getRange(lr+1, 2,data_clean.length,9).setValues(data_clean);
clear_range.clear();
}
This code checks the range B4:J100 for a value in Column B.
If there is a value and the script is run, it copies those rows onto dstSheet.
My role is marking invoices as paid or not.
The dstSheet will already contain the data, which is pulled back into the srcSheet with a query. Column K is not part of the original query.
If I mark a row as "PAID" in column K on the srcSheet, I want the code to take the data_data variable and overwrite what is already in the dstSheet, so that the query then pulls the data back into srcSheet with column J then showing "PAID".
It means I can then change column K to "NOT PAID", run the script again and it will over-write the "PAID".
This makes better sense than my last post and I am so close to achieving what I need, just stuck on this last bit.
If you simply want to monitor the changes between the two mentioned sheets, it would be much easier to use an onEdit(e) trigger which will tell you which cell has been edited.
Snippet
function payINVOICE(e) {
var srcSheet = SpreadsheetApp.getActiveSheet(); //gets the active sheet which is supposed to be source sheet
var dstSheet = SpreadsheetApp.openById('DEST_SHEET_ID').getSheetByName('INVOICE_ENTRY'); //gets the destination sheet
if (e.range.getSheet().getName() == 'INVOICE_ENTRY' && e.range.getColumn() == 11) { //e specifies where the edit has been made - therefore this if condition checks if the edit is in the INVOICE ENTRY sheet and if the column is the K column
var row =e.range.getRow(); //this gathers the row at which the edit has been made
var data = srcSheet.getRange(row, 2, 1, 10).getValues(); //this gathers the data corresponding to the row at which the edit has been made
dstSheet.getRange(row, 2, 1, 10).setValues(data); //this sets the data into the corresponding row in the destination sheet
}
Explanation
The above code uses the onEdit(e) installable trigger and the e event object. In this way, when an edit is being made on the srcSheet on the 11th column (aka K column) and the sheet name is "INVOICE_ENTRY", then the row at which the change has been made is kept in the row variable. Afterwards, the corresponding row of data is kept in the data variable; the getRange(row, 2, 1, 10) references the range for the row at which the change on the K column has been made. In order to update the dstSheet, the data value is set to the according range using setValues(data).
Installing the trigger
To make the payINVOICE(e) function trigger on an edit action, you need to install an onEdit trigger.
This is being done by accessing the project's triggers by clicking this icon:
After that, you just need to create a new trigger by clicking the Add trigger button and create a trigger with the following settings:
Trying the function
In order to try the behavior for this, you just need to make an edit on the srcSheet on the K column and this change will be reflected in the destSheet.
Note
The ranges that have been used in this script are chosen considering the fact that:
K column consists of the PAID/NOT PAID text;
The srcSheet and the dstSheet have the data wanted in the same ranges.
You might need to customize these according to your sheet and add the needed formulas/filters you have mentioned.
Reference
Apps Script Installable Triggers;
Apps Script Event Objects.
I have a database loaded from a google spreadsheet:
mydatabase = sheet.getDataRange().getValues()
which then I extend with a new record:
mydatabase.push(mydatabase[x])
and then at the end of the script, I would want to write back the entire database to the google spreadsheet but the
sheet.getDataRange().setValues(mydatabase)
gives me ERROR since the new database is one record higher than the original was when loaded.
Is there any way to force the getDataRange() to write back the database into the sheet? The spreadsheet otherwise would have enough rows to accommodate the bigger dataset.
In general, for .setValues(obj[][]) to work as expected, the Range that it is acting on must be the same size as the obj[][].
Commonly, this is ensured by acquiring a new Range from the desired Sheet:
var ss = SpreadsheetApp.openById("some id");
var sheet = ss.getSheetByName("some name");
var db = sheet.getDataRange().getValues();
/*
* Do some operations on the obj[][] that is db
* these operations can include adding / removing rows or columns.
*/
// If db is smaller than existing "written" data, the existing data should be cleared first.
if(db.length < sheet.getLastRow() || db[0].length < sheet.getLastColumn())
sheet.clearContent();
// Write the current db back to the sheet, after acquiring the
// exact number of rows and columns needed to hold the values.
sheet.getRange(1, 1, db.length, db[0].length).setValues(db);
I am trying to create a time-based/triggered lock on data in cells of a spreadsheet so that the data in the cell is not changed during certain periods of the day. But before I created the time-lock to remove the range protection I need to first lock the cell when the first POST is made to it.
How do I lock a cell from being edited after the first POST so that consecutive POSTs to the same cell during the lock period do not change the data?
Note: The app is executed as me and anyone including anonymous has access to it.
doPost(e)
{
ssNew = SpreadsheetApp.openById(ssId);
var sheet = ssNew.getSheets()[0];
var cell = sheet.getRange(1,1);
var visits = cell.getValue();
cell.setValue(e.parameter.name);
SpreadsheetApp.flush();
var lockrange = sheet.getRange(1,1);
var protection = lockrange.protect().setDescription('Protected');
var me = Session.getActiveUser();
protection.removeEditor(me);
}
You'll want to store a flag in some kind of persistent memory: like using the PropertiesService (probably script properties in this case) or a spreadsheet cell in another sheet. The doPost() could then check if that flag is set before changing it.
You could also look at programmatically protecting the cell against edits by users.
Is it possible to write a google script that will delete a row in a Google Sheet, based on cell values for a given range, in another google sheet?
I've done some research on how to do this all in the same google sheet, but my limited experience with writing google sheet scripts has prevented me from understanding if what I described is possible, and how.
This is a script I have so far. This will delete rows in a range of my active spreadsheet if a cell in Column F contains the word "id:123456". What I'd like to be able to do, is to modify this code so that if the word "id:123456" is found, it will look in another column of another Google Sheet, and delete rows that contain "id:123456".
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName('Sheet1'); // change to your own
var values = s.getDataRange().getValues();
for (var row in values)
if (values[row][6] == 'id:123456')
s.deleteRow(parseInt(row)+1);
};
I haven't tested this code, so you might need to do some modifications to run successfully. But the basic idea is simple: have two sheets open at once, and based on the if from the first sheet make changes in the second sheet.
var ss1 = SpreadsheetApp.getActiveSpreadsheet();
var ss2 = SpreadsheetApp.getByID(0); //Change to the other sheet's ID, or somehow open it.
var s1 = ss1.getSheetByName('Sheet1'); // change to your own
var s2 = ss2.getSheetByName('Sheet1'); // change to your own
var values = s1.getDataRange().getValues();
for (var row in values)
if (values[row][6] == 'id:123456')
var other_values = s2.getDataRange().getValues();
for (var other_row in other_values)
if (other_values[row][6] == 'id:123456')
s2.deleteRow(parseInt(other_row)+1);
Main thing to be worried about and test: I copied your delete code, but since we're deleting rows, unless we start from the bottom we might start missing. (Suppose rows 4 and 8 out of 10 contain something to be deleted; you might end up deleting 4, which turns 8 to 7, but then we still delete row 8--meaning that we've missed a row we should have deleted and deleted a row we shouldn't have.)
I noticed there is quite a number of questions here regarding protection on cells in a spreadsheet.
But there seems to be no viable solution.
For example, column 'A' can only be edited by person1#email.com, and column 'B' can only be edited by person2#email.com.
There seems to be an issue tracker on google site since 2013...but Google has not come up with an API for it yet.
Does anyone have a workaround?
The code below only works for entire page protection..
sheet.setSheetProtection(permissions);
Use an onEdit() function that checks what user is editing the Sheet, then check what column is being edited. Have an object of user names, and what columns they can edit. If a user is not allowed to edit, undo the change.
You can only undo the change if you have a way of knowing what the last cell value was. There is no undo method in Apps Script, or other built in way to get the old value with Apps Script. But there is a way to configure the data to achieve a way to undo the edit.
Have a central sheet with all formulas referring to other sheets. In other words, the data that people view is a copy of the stored data in another sheet. Divide the data into sheets according to who can edit what. The code will write data to the correct sheet when a cell is edited.
Basically, you would have sheets that are the database where the data is stored. Those sheets could even be hidden, and of course they would be protected.
The viewing and editing would be done in a separate sheet from the sheets that are the official data storage.
So, the sheet that people are viewing and editing is the "User Interface"; it's the "Front End" of the "App". The sheets that are the official data storage are the "Back End".
function onEdit(e){
Logger.log("e.value: " + e.value);
Logger.log("e.range.getRow: " + e.range.getRow());
Logger.log("e.range.getColumn: " + e.range.getColumn());
var objWhoCanEditWhat = {"user1":"[A,B]", "user2":"[A]"};
//Get this user
var thisUserIs = Session.getActiveUser().getEmail();
Logger.log('thisUserIs: ' + thisUserIs);
Logger.log('Index of #: ' + thisUserIs.indexOf("#"));
thisUserIs = thisUserIs.substring(0, thisUserIs.indexOf("#"));
Logger.log('thisUserIs: ' + thisUserIs);
var whatColumnCanEdit = objWhoCanEditWhat[thisUserIs];
Logger.log('whatColumnCanEdit: ' + whatColumnCanEdit);
var editedColumn = e.range.getColumn();
var editedRow = e.range.getRow();
Logger.log('editedColumn: ' + editedColumn)
var ss = SpreadsheetApp.getActiveSpreadsheet();
//There must be a way to determine what sheet needs to be accessed, and that sheet name
//is set dynamically.
var objColumnEditedToSheetName = {"ColA":"Sheet6TY", "ColB":"SheetColumnB"};
var whatSheetToUse = objColumnEditedToSheetName[editedColumn];
if (whatColumnCanEdit != editedColumn) { //If the column this user can edit is not the same as
//the column that just was edited, then
//Undo the change with this code
//Retrieve the old official data from the data storage sheet
var sheet = ss.getSheetByName(whatSheetToUse);
} else {
//If the user is allowed to edit this column, write the data to the official data storage sheet
var sheet = ss.getSheetByName(whatSheetToUse);
};
//Always put a formula back into the cell that was just edited in order
//to show data from the back end data source
var viewSheet = ss.getSheetByName("SheetForEditingAndViewing");
//You know the row and column of the cell that was just edited, so use that to
//reference what cell to put the formula back into.
viewSheet.getRange(editedRow, editedColumn).setFormula("Sheet1!A3");
};