As of now, it appears that Google Spreadsheets does not allow locking of cell ranges but instead locking has to be done on a sheet by sheet basis in its entirety. I would like to share a sheet with another user and have them enter data on Sheet 1. To prevent tampering, I would like the data duplicated on Sheet 2 (locked by me) with a timestamp of the last change made on Sheet 1. I've been playing with this onEdit() function but it does not give an updated time when I edit Sheet 1 but still only if I edit Sheet 2. I just haven't figured out what I'm doing wrong.
function onEdit(e)
{
var ss = e.source.getActiveSheet();
var rr = e.source.getActiveRange();
//comment 2 lines below if you want it working on all sheets, not just on 2nd one
if(ss.getIndex()!= 2)
return;
///
var firstRow = rr.getSheetByName(Sheet2).getRow();
var lastRow = rr.getSheetByName(Sheet2).getLastRow();
//the last modified date will appear in 12th column
for(var r=firstRow; r<=lastRow; r++)
ss.getRange(r, 12).setValue(new Date());
}
Is there another way I could do this?
You cannot use scripts to prevent the other user from tampering, because
he can go to Tools>Script editor and edit your script so that it
becomes idle, then edit Sheet1, then restore your script.
See the issue tracker for this issue. You cannot protect your scripts
from being edited by other users with whom you share at least one worksheet of your spreadsheet.
On the other hand, leaving scripts aside, you can always see
the history of what was edited by whom and when: File>See revision history.
I see a couple of problems. First, you're trying to use .getSheetByName() on a range object, but that method is only supported by spreadsheet objects. You can run .getSheet() on a range and that will return the sheet the range is in. Secondly, formatting: GAS requires standard Javascript which means that your if and for functions need to have {}. This should work for what you want:
onEdit(e){
if(e.range.getSheet().getSheetName()!="Sheet2"){
var row1=e.range.getRow();
var col2=e.range.getColumn();
var row2=e.range.getLastRow();
var col2=e.range.getLastColumn();
var data=e.range.getValues();//the data to be copied
var copySheet=e.source.getSheetByName("Sheet2");//sheet you want to copy to
var copyRange=copySheet.getRange(row1,col1,row2-row1,col2-col1);// the range data will be copied to
var time=new Date();
var timeRange=copySheet.getRange(row,col);//wherever you want the time signature to be.
timeRange.setValue(time);//inserts time stamp into sheet
copyRange.setValues(data);//copies data to the range
}
}
Just replace row and col with the row and column you want to place the time into. It takes the edited range and copies it to the same location on sheet 2 so it only works if sheet 1 is the only sheet to be edited. Otherwise you'll end up with overlapping data. You could also set a time stamp to be adjusted to the range, meaning put its position as something like (row1,col2+5). That would put it 5 columns right of the edited range.
Related
I need to copy the values of multiple ranges of data from my main spreadsheet into another spreadsheet and then copy a different range of data back to the main spreadsheet.
Here are my example spreadsheets:
The main spreadsheet (two sheets: 1 - A sheet of pasted values alongside columns of user comments. 2 - A sheet with a button that runs the Apps Script)
The live data spreadsheet (pulls and formats the required data from other sheets).
Previously, I’ve used .copyTo() to copy the values of data and paste them elsewhere on the same spreadsheet. However, this method can’t be used to copy data to a different spreadsheet. Copying data from multiple ranges is also causing me issues. Here is my code:
function RefreshSheetData() {
// 1a. Run the script when a button is pressed (Main Spreadsheet - ‘Update report button!A3’)
// 1b. Run the script at a certain time (every Monday at 10 AM)
// 1c. Run the script when data in a sheet is replaced with new data via a formula – NOT POSSIBLE.
// 2. Copy email address and user comments (Main Spreadsheet - Editable report - J3:J & AM3:AR)
// 3. Clear the “Updated user comments” sheet below the header row (Live data Spreadsheet - Updated user comments - A2:G)
// 4. Paste the values (Live data Spreadsheet - Updated user comments - A2:G)
// 5. Clear the “Editable report” below the header rows (Main Spreadsheet - Editable report – B3:AR)
// 6. Copy the Live sheet (which should now include the most recent user comments via array vlookup) (Live data Spreadsheet - live data – A3:AQ)
// 7. Paste the values (Main Spreadsheet - Editable report - B3:AR)
// 8. Add the (United Kingdom) time and date (Main Spreadsheet - Update report button - A10)
// 9. Add the time and date (Live data Spreadsheet - Updated user comments - J1)
ScriptApp.newTrigger('RefreshSheetData')
.timeBased()
.onWeekDay(ScriptApp.WeekDay.MONDAY)
.atHour(10)
.create();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var startSheet = ss.getSheetByName('Editable report');
var sourceRange = startSheet.getRangeList(['J3:J', 'AM3:AR']);
var sourceValues = sourceRange.getValues();
var target = SpreadsheetApp.openById('1OHQHefYvE4vZZPr8jgziy_L3-UBf1WSoKzMWQ8LUz6w');
var targetSheet = target.getSheetByName('Updated user comments');
var clearTargetRange = targetSheet.getRange('A2:G').clearContent();
var targetRange = targetSheet.getRange('A2').setValues(sourceValues);
var liveTargetSheet = target.getSheetByName('Live data');
var liveSourceRange = liveTargetSheet.getRange('A3:AQ').getValues();
var clearMainRange = startSheet.getRange('B3:AR').clearContent();
var startRange = startSheet.getRange('B3').setValues(liveSourceRange);
SpreadsheetApp.getActive().getRange('A10').setValue(new Date());
targetSheet.getRange('J1').setValue(new Date())
}
Any tips would be appreciated. Thank you.
Part of my question was how to trigger a script when a formula output changes. That’s not possible. Neither .onEdit or .onChange triggers work as they only respond to user actions. They won't run when the value of an IMPORTRANGE or alternative formula changes.
Here some suggestions referring to each of the steps mentioned in your Apps Script file
1b. To run the script at a certain time: please use the installable trigger "Time driven"
1c. To run the script when data in the sheet is updated: please use the onEdit trigger
1d. You can combine all triggers by simply adding as many, as required.
To copy and paste data, you just need the methods getValues() and setValues(), which you were using already, the important thing is that you chose the "to copy" range correctly.
You can clear a range with clear()
See 3.
You can copy a sheet with copyTo(), however keep in mind that if using this method, your data in the copied sheet will automatically be updated if there is a change in the original sheet. If you want the values to remain static, you have to copy and paste them with copyValues() and setValues().
See 2. and 5.
See here how to get and format the date in Apps Script
Assign the date to a variable and use setValue()
I encourage you to try and build the script based on those steps
yourself, the Apps Script documentation provides you good reference
and guidance, how to do so. If you encounter specific problems during
one of the steps which you cannot solve with the documentation, feel
free to ask!
I am working with Google Form results data that is being dropped into a Google Sheet tab and I added a column to calculate the percentage of the quiz scores that are then being pulled onto a tracking tab in the same sheet that calculates their percentage completed. Right now the Percent Column is appearing as a blank field no matter which formula I've tried. I am wondering if there is a different formula would work that would automatically apply to the column when new responses are added? Or would a Google Script be a better option?
I am wanting to keep the raw result data on the same sheet since it is compiling all of the quizzes into one Google Sheet with one tab pulling the Percentages to show completion rate.
I have tried ARRAYFORMULA and the formula that works if you copy it manually to each entry is "=left(C2,find("/",C2)-1)/(right(C2,len(C2)-find("/",C2)))"
Try
=Arrayformula(if(len(C2:C), left(C2:C,find("/",C2:C)-1)/(right(C2:C,len(C2:C)-find("/",C2:C))),))
and see if that works?
This function will add a formula in the last column when a new formresponse will be added to the sheet:
function setFormula() {
var ss=SpreadsheetApp.openById('SHEET_ID');
var sheet=ss.getSheetByName('SHEET_NAME');
var lastRow = sheet.getLastRow();
var formulaRange1 = sheet.getRange(lastRow, sheet.getLastColumn());
formulaRange1.setValue('=IF($A'+lastRow+'="";"";TODAY()-$Q'+lastRow+')');
}
Your formula must be adjusted accordingly. Just make sure that lastRow is inside the string instead of the line number and add a onFormResponse Trigger. I've added this script to the form, not to the spreadsheet.
I'm using a Google script then sends out an email when a certain column in a Google sheet is changed. The information in the cell is either inputted manually, or completes using a formula based on information in other cells.
The script works fine when information is manually entered, but not when the formula runs. I've read up on it and realise that a formula calculation doesn't count as an edit, so how do I get the script to run?
It's currently set up to trigger from the spreadsheet when there's an edit.
Below is the part of my script that covers the column/cell in question.
function sendEmail() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var row = sheet.getActiveRange().getRow();
var cellvalue = ss.getActiveCell().getValue().toString();
if(sheet.getActiveRange().getColumn() == 12)
There's a lot more included in the script so I haven't copied everything onto here. Many thanks in advance.
There is no trigger that can run when a formula changes.
Try figure out, what conditions is your formula depends on:
if it is another cell, entered manually, then use those cell to trigger it's changes with onEdit
if the formula imports data from external source, use random or time functions, you'd better use onTime trigger.
if the formula uses importrange then go to the range you import and see the original range, return to step 1 → 2...
I'm an elementary teacher with limited coding experience but I enjoy trying to figure things out if it simplifies my life. :)
I created a Google Form for my students to make their daily lunch choice.
I set up a script that has a trigger to clear the form between midnight and 1 am each night, and it looks like this:
function clearRange() {
//replace 'Sheet1' with your actual sheet name
var sheet = SpreadsheetApp.getActive().getSheetByName('Lunch');
sheet.getRange('A2:G25').clearContent();}
The script itself works - however, now, whenever new entries are added, they are added to the spreadsheet row right below where the old entries were cleared. Thus, if I have twenty students responding on Monday, the spreadsheet has rows 2-21 filled out, which get cleared sometime overnight Monday/Tuesday AM, and the next day, the responses fill rows 22-42, etc. etc.
How can I edit or add something to my script that forces it to put all new entries, after the old ones are cleared, on Row 2 of my spreadsheet?
If this question has already been answered, please point me in that direction as well.
Thank you!
Instead of clearing content from cells, you need to delete rows containing form submission.
Here is the modified code which should work for you. Also, make sure you do not have any frozen row/s.
function clearRange() {
//replace 'Sheet1' with your actual sheet name
var sheet = SpreadsheetApp.getActive().getSheetByName('Lunch');
//get last row, this will be used to count how many rows to delete
var lr = sheet.getLastRow();
//delete row from 2 to last row
sheet.deleteRows(2, lr-1);
}
You're using the .clearContent() method and Google Forms keeps track of the responses based on the row in your spreadsheet, you need to delete the row completely using deleteRows(rowPosition, howMany), like this:
function clearRange() {
//replace 'Sheet1' with your actual sheet name
var sheet = SpreadsheetApp.getActive().getSheetByName('Lunch');
sheet.deleteRows(2, 25);
}
Note: This will not restart the counter you see in the Google Forms user interface:
In order to do that, you will need to use the deleteAllResponses() using the FormApp Class
Thank you very much in advance for your help, I'm new to coding but proficient with standard Excel functions. I would greatly appreciate any input on this project.
I want to create a Google spreadsheet that has 3 sheets. The first is a DATA sheet which lists stock tickers and provides live prices via Google finance {=googlefinance(VTI,price)}.
The second sheet is the MASTER sheet that aggregates all of the positions, including number of shares in stocks, quantity in fixed income instruments, quantity in bullion, etc. The prices used to calculate current market value of positions are drawn from the DATA sheet. All values are added together to create a total value cell, E57, that updates itself automatically from the google finance data throughout the trading day. This all works fine.
The final sheet is the HISTORY sheet. Here's what I want to do. I want cell E57 to copy to this sheet once a day at market close so I have a daily history of the aggregate portfolio. Each time the script copies and pastes the value to the HISTORY sheet, it needs to paste on the next available row in the same column. So far, I've written a script that successfully copies and pastes the value at a defined time interval (using Project Triggers), but it just keeps pasting over the previous value. How can I make it paste to each successive open cell and generate a list?
Also, I need all of this to work without me signing in or opening/activating the sheet. I want it to run completely autonomously, that's why I'm activating the sheet via openById instead of using the ActiveSheet code (I think that reasoning is correct, but not sure).
Here's the script I have:
function PasteValue() {
var ss = SpreadsheetApp.openById("0Ao2pCtssx6TcdGpDWFpSXy1pUXA3MlAtSjZFVHlaZVE");
ss.getRange("MASTER!E57").copyTo(ss.getRange("HIST!C5"),{contentsOnly:true});
}
What do I do to improve?? Thank you!
Your code suggests the name of the history sheet is HIST.
I think there are a few ways to do this:
Option 1:
Uses the getLastRow() method
function PasteValue() {
var ss = SpreadsheetApp.openById("0Ao2pCtssx6TcdGpDWFpSXy1pUXA3MlAtSjZFVHlaZVE");
var nextRow = ss.getSheetByName('HIST').getLastRow()+1;
ss.getRange("MASTER!E57").copyTo(ss.getRange("HIST!C" + nextRow),{contentsOnly:true});
}
Note that option 1 will not work if you have a column of cells that contain formulas that have been copied down to the bottom of the sheet.
Option 2:
Loops through the cells in column C to find the first blank cell.
function PasteValue() {
var ss = SpreadsheetApp.openById("0Ao2pCtssx6TcdGpDWFpSXy1pUXA3MlAtSjZFVHlaZVE");
var values = ss.getRange("HIST!C:C").getValues();
for (vari=0; i<values.length; ++i) {
if (values[i][0] == "") {
var nextRow = i+1;
break;
}
}
ss.getRange("MASTER!E57").copyTo(ss.getRange("HIST!C" + nextRow),{contentsOnly:true});
}
Option 2 should work as long as you have no blank cells in column C.
Option 3:
Use the appendRow() method
Good luck!