I have a data dashboard in Google Sheets that synthesizes data from several different data sources. These data sources are dropped into the same Google Sheet file, each on separate tabs. Different team members are responsible for updating each of the individual data tabs, which is done by pulling reports from various programs and pasting the new data into their respective data tab.
Note: I realize this process of pasting in data from reports from different programs is inefficient. Unfortunately the systems we use don't all speak to one another, so this is the best way we've found so far to analyze data across our systems.
The goal: I would like to have a cell on each data tab where it lists the last date and time that individual tab (sheet) was updated. For example, if I update the data on Tab 1, I would like the date/time stamp to update on Tab 1 but not on Tab 2. The date/time stamp on Tab 2 should only update when I add new data to Tab 2.
I have tried the script pasted below (from the Question/Answer linked here), and it does update the date/time on the tab:
function onEdit(e) {
var s = e.source.getActiveSheet();
var sName = s.getName();
var ar = e.range;
var row = ar.getRow();
var arRows = ar.getNumRows()
if( ar.getColumn() == 3 && sName == 'Sheet1') {
s.getRange(row,6,arRows).setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm");
}
}
The problem is that it also updates the date/time on Tab 1 when I update the data on Tab 2.
What script do I use so that it updates the date/time on Tab 1 only when the data on Tab 1 is updated? And would update the date/time on Tab 2 when the data on Tab 2 is updated?
Change date cell A1 in every Sheet that is edit by a user
function onEdit(e) {
var sh = e.range.getSheet();
sh.getRange(1,1).setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm");
}
If you wish to exclude some sheets:
function onEdit(e) {
const excl = ['Sheet1','Sheet2'];
var sh = e.range.getSheet();
if(~excl.indexOf(sh.getName()))return;//this could allow you to exclude some sheets
sh.getRange(1,1).setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm");
}
I chose A1 in this case
Related
I have a sheet "Sheet1" where a user inputs data in cells A3:E3. The goal is to then move that data into "Sheet2" where column A4:A34 is a numeric value for the day of the month. I want to move the data inputted on "Sheet1" into "Sheet2" on the row that corresponds with the current day of the month. Then "Sheet1" would clear itself for the next day ahead.
I'll be honest I'm not great with functions and I'm mainly looking for help on where to look to find a solution. I've tried different code that I do know how to use like Query but the issue is it doesn't lock the value in place and as soon as the values are wiped in Sheet1 they are then removed via the query.
You will need to run a function on a time-driven trigger. The function can use Range.getValues() to read the data, get the current date's day number with const dayNumber = new Date().getDate(), look up the day number in Sheet2 with Range.getValues().flat().indexOf(dayNumber), write the data with Range.setValues() and finally clear the source range with Range.clearContent().
It is unclear what the purpose the day numbers in Sheet2 serve, because the data would in any case be written once a day. It would seem simpler to always append the most recent data at the first available row and include a timestamp.
For an example of how to do this, see the appendRowsToArchiveSheet script.
Since the task is rather simple, here is the code that does the work:
// add menu Scripts
function onOpen() {
SpreadsheetApp.getUi().createMenu('🔨 Scripts')
.addItem('✅ Copy to archive', 'copy_range_to_archive')
.addItem('❌ Erase', 'erase_range')
.addToUi();
}
// erase the range A3:E3
function erase_range() {
SpreadsheetApp.getActiveSpreadsheet()
.getSheets()[0]
.getRange("A3:E3")
.clearContent();
}
// copy the range A3:E3 to Sheet 2
function copy_range_to_archive() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const src_range = ss.getSheets()[0].getRange("A3:E3");
const data = src_range.getValues();
const dest_sheet = ss.getSheets()[1];
const today = new Date().getDate();
const dest_range = dest_sheet.getRange(3 + today, 2, 1, data[0].length);
dest_range.clearContent();
dest_range.setValues(data);
}
It makes the menu 'Scripts' and two command 'Copy to archive' (the range A3:E3) and 'Erase' (the same range).
I have a spreadsheet where users can enter data and then execute a function when clicking on a button. When the button is clicked it logs the time and entered data in a new row on another sheet in that spreadsheet.
To make sure that sheet is not accidentally edited by the users I want to create a non-shared backup of that data.
I import the range to another spreadsheet, but just importing the range means that if the original sheet is edited/erased that data will also be edited/erased, so I wrote the following script to log the changes as they come in.
function onEdit(event){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var incomingSheet = ss.getSheetByName('Incoming');
var lastRow = incomingSheet.getLastRow();
var incomingData = incomingSheet.getRange(lastRow,1,1,7);
var permanentSheet = ss.getSheetByName('PermanentLog')
var newdataRow = permanentSheet.getLastRow();
incomingData.copyTo(permanentSheet.getRange(newdataRow+1,1));
}
This works when Run from the Apps Script Editor, however, when I enter new data and click the button on the original spreadsheet, it logs the data to the log sheet there, and the range is imported to the 'Incoming' sheet of the new Spreadsheet, but the data is not copied over to the 'Permanent Log' sheet (unless I Run it manually from within the Apps Script Editor). It also works if I remove the ImportRange function from the first sheet and then just manually enter data in on the 'Incoming' sheet.
So does this mean new rows from an Imported Range do not trigger onEdit? What would be the solution? I don't want to run this on a timed trigger, I want to permanently capture each new row of data as it comes in.
Also, am I overlooking a more elegant and simple solution to this whole problem?
Thank you for your time.
This function will copy the data to a new Spreadsheet whenever you edit column 7 which I assume is the last column in your data. It only does it for the sheets that you specify in the names array. Note: you cannot run this from the script editor without getting an error unless you provide the event object which replaces the e. I used an installable onEdit trigger.
The function also appends a timestamp and a row number to the beginning of the archive data row
function onMyEdit(e) {
e.source.toast('entry');//just a toast showing that the function is working for debug purposes
const sh = e.range.getSheet();//active sheet name
const names = ['Sheet1', 'Sheet2'];//sheetname this function operates in
if (~names.indexOf(sh.getName()) && e.range.columnStart == 7) {
const ass = SpreadsheetApp.openById('ssid');//archive spreadsheet
const ash = ass.getSheetByName('PermanentLog');//archive sheet
let row = sh.getRange(e.range.rowStart, 1, 1, 7).getValues()[0];
let ts = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy/MM/dd HH:mm:ss");//timestamp
row.unshift(ts, e.range.rowStart);//add timestamp and row number to beginning
Logger.log(row);//logs the row for debug purposes
ash.appendRow(row);//appends row to bottom of data with ts and row
}
Logger.log(JSON.stringify(e));
}
Restrictions
Script executions and API requests do not cause triggers to run. For example, calling Range.setValue() to edit a cell does not cause the spreadsheet's onEdit trigger to run.
https://developers.google.com/apps-script/guides/triggers
So yeah, as far as I understand you it can't be done that way.
I am just a couple of weeks into my coding adventure. I am trying to build a sheet that will edit values in existing cells based on the value in another cell. I was able to write a script that would write a new row in the database based on the values in the dashboard when the script was run.
function submitClientContact() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("New Client Form"); //Form Sheet
var datasheet = ss.getSheetByName("Info for Sales"); //Data Sheet
//Input Values
var values = [[formSS.getRange("E4").getValue(),
formSS.getRange("E3").getValue(),
formSS.getRange("E5").getValue(),
formSS.getRange("C8").getValue(),
formSS.getRange("C9").getValue(),
formSS.getRange("C10").getValue(),
formSS.getRange("C11").getValue(),
formSS.getRange("C12").getValue(),
formSS.getRange("C13").getValue(),
formSS.getRange("C14").getValue(),
formSS.getRange("C16").getValue(),
formSS.getRange("C17").getValue(),
formSS.getRange("C18").getValue(),
formSS.getRange("C19").getValue(),
formSS.getRange("C20").getValue(),
formSS.getRange("C21").getValue(),
formSS.getRange("C22").getValue(),
formSS.getRange("C24").getValue(),
formSS.getRange("C25").getValue(),
formSS.getRange("C26").getValue(),
formSS.getRange("C27").getValue(),
formSS.getRange("C28").getValue(),
formSS.getRange("C29").getValue(),
formSS.getRange("C30").getValue(),
formSS.getRange("E8").getValue(),
formSS.getRange("E9").getValue(),
formSS.getRange("E10").getValue(),
formSS.getRange("E11").getValue(),
formSS.getRange("E12").getValue(),
formSS.getRange("E13").getValue(),
formSS.getRange("E14").getValue(),
formSS.getRange("E16").getValue(),
formSS.getRange("E17").getValue(),
formSS.getRange("E18").getValue(),
formSS.getRange("E19").getValue(),
formSS.getRange("E20").getValue(),
formSS.getRange("E21").getValue(),
formSS.getRange("E22").getValue(),
formSS.getRange("E24").getValue(),
formSS.getRange("E25").getValue(),
formSS.getRange("E26").getValue(),
formSS.getRange("E27").getValue(),
formSS.getRange("E28").getValue(),
formSS.getRange("E29").getValue(),
formSS.getRange("E30").getValue()]];
datasheet.getRange(datasheet.getLastRow()+1, 1, 1, 45).setValues(values);
var sheet = SpreadsheetApp.getActive().getSheetByName("New Client Form");
sheet.getRange("E3:E5").clearContent();
var sheet = SpreadsheetApp.getActive().getSheetByName("New Client Form");
sheet.getRange("C8:C30").clearContent();
var sheet = SpreadsheetApp.getActive().getSheetByName("New Client Form");
sheet.getRange("E8:E30").clearContent();
}
I now need a script to edit the cells in the database. The sheet will have 3 primary functions. 1 dropdown containing all of the clients in the database (cell A1), 1 row to show the data that is in the database for that client (column A), and one row that would be used to edit that existing data (column B). I can build the lookup equations easily, but I need help with the script to edit the database.
I basically need a script that will use the data in the drop down (A1) to reference the row in the database in which data needs to be edited, followed by editing all 45 columns but leaving blank cells (blank in column B) alone.
I assume it is just a couple of lines that I am missing to make this work, but I have been struggling with this for far too many hours.
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");
};
I need help please - here's a little history..
I have a google schedule - https://docs.google.com/spreadsheet/ccc?key=0ArmKEmhue9OgdEFwMHBldFpGenNVdzJUQThCQU9Qcmc&usp=sharing
Within that schedule there are numerous sheets but the ones that I was hoping to get help with is the Form Responses 1 and PROJECTS sheet
I have a google form - https://docs.google.com/forms/d/1yFy3i5H3abhFjdvchuJq2ARODcfRGo6KNkOAgeRIMMU/viewform
It's linked to the google schedule and with each submission the data goes in the Form Responses 1 sheet -
There is a script applied to the spreadsheet -
COPYING TO/FROM SCRIPT
/**
* A function named onEdit will be called whenever
* a change is made to the spreadsheet.
*
* #param {object} e The edit event (not used in this case)
*/
function onFormSubmit(e){
var copyFromRange = 'Form Responses 1!A2:AC999';
var copyToRangeStart = 'PROJECTS!A71:AC999';
copyValuesOnly(copyFromRange, copyToRangeStart);
}
/**
* This function will copy the values from a given range to
* a second range, which starts from the given cell reference
*
* #param {string} copyFromRange Range reference eg:
* #param {string} copyToRangeStart Cell reference eg:
*/
function copyValuesOnly(copyFromRange, copyToRangeStart) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getRange(copyFromRange);
source.copyTo(ss.getRange(copyToRangeStart), {contentsOnly: true});
}
This is allowing the data to copy over to the PROJECTS sheet so multiple users can edit and update BUT realized that it overrides the data each time a new submission comes in
Also just to note within the Form Responses 1 sheet under column C "Project Number" there is a forumula =ArrayFormula("MC"&TEXT(ROW(A2:A)-1 ; "000") ) that I placed to allow for automatically numbering each submission with a project number
I also have a ARCHIVE script -
Once a project is final, under column G "Archive" if you type "DONE" it then moves to the ARCHIVE sheet
function onEdit() {
// moves a row from a sheet to another when a magic value is entered in a column
// adjust the following variables to fit your needs
// see https://productforums.google.com/d/topic/docs/ehoCZjFPBao/discussion
var sheetNameToWatch1 = "PROJECTS";
var sheetNameToWatch2 = "ADVERTISING";
var columnNumberToWatch = 7; // column A = 1, B = 2, etc.
var valueToWatch = "DONE";
var sheetNameToMoveTheRowTo = "ARCHIVE";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if ( (sheet.getName() == sheetNameToWatch1 || sheet.getName() == sheetNameToWatch2) && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
}
}
ISSUES -COPYING - When a new submission is entered using the form, the data comes in Form Responses 1 sheet and then copies to PROJECTS sheet BUT you cannot edit the data under PROJECTS because each submission overrides any edits and brings in the original form submission data -
What I would like to have happen is the data comes in Form Responses 1 sheet then copies to PROJECTS sheet on each submission but then does not override when a new submission comes in and any changes to the data under PROJECTS stays there
ISSUES - PROJECT NUMBER - the formula setup under Form Responses 1 sheet under column C row 2 - each row has a project number assigned and then it copies to the PROJECTS sheet, when the row is then archived ("DONE) and moves to the ARCHIVE sheet the project number assigned to that row goes with it BUT when you submit a new project the same number can be applied to a new project so then ultimately when you archive all the projects many will have the same project number, is there a way to make each project have a unique project number?
*ISSUES - SUBMIT FORM * - I am not sure why but when a new submission comes in the data is coming in at row 113 each time, not at the end of the form responses rows?
Any help would be greatly appreciated - I thought it was going to work but seems the script isn't the right setup for what I would like it to do
Thanks so much,
Paola G
Here's some pseudo-steps based on how I handle this kind of situation:
onEdit trigger runs when an edit is made to the Spreadsheet
onEdit checks your range for the value that you're looking for. If the value(s) match, proceed to step 3. Otherwise, script does nothing.
Get the row number where the value is changed to the one you're looking for.
Get the Range of that row up to/including whatever column you want to stop at.
Create an Array (or Object) that is a copy of all that data.
"Paste" (add a new row) to the 'completed' Spreadsheet that is the data in that Array (or Object).
Erase the values in the old Spreadsheet.
However, because Form responses are critically tied to the response Spreadsheet, moving rows as you are trying to do is not the best way to go about the problem.
In fact, I would dare to say that you should not be moving at all - instead, what you can do is copy the values from the row you're looking for "DONE" in, and then simply hide that row in the response Spreadsheet. That way, you can think of rows that are not hidden as "still need to be finished.", and projects that are finished will not only be in the response Spreadsheet for "archival/data integrity" purposes, but also in your "completed" Spreadsheet that you can make public-facing, etc. without needing to worry about your Form responses being modified.
Personally I use this strategy for scheduling project reservations for foreign language faculty at my university, and it greatly simplifies the "extra work" required in managing two spreadsheets concurrently. Instead, just hide values you don't need to see and post them to a new Spreadsheet that can act as a record of what's important, all while being able to edit the layout of the Spreadsheet.
PS - If you didn't notice already, you can't break the integrity of the response Spreadsheet, otherwise the Form link would be broken. In fact, Google won't let you make layout-changing edits to response Spreadsheets that are linked to a Form.
I got a script thanks to Riël Notermans (Zzapps)
https://plus.google.com/u/0/116507112418725270540/posts/1i75t1wFEfh
function onFormSubmit(e){
var responses = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form Responses 1");
var projects = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("PROJECTS");
var lastrow = responses.getLastRow();
var col = responses.getLastColumn();
var row = responses.getRange(lastrow, 1, 1, col).getValues();
projects.appendRow(row[0]);
//copyValuesOnly(copyFromRange, copyToRangeStart);
}
Make sure there are no empty rows beneath both sheets, there are some project numbers. Just make sure the last row is the data.