Google Sheets new sheet templating - google-apps-script

I'd like every new added sheet (when user presses "+" in lower left corner) to contain some templated information (headers for table). I'm not talking about whole document template, I'd like to make it only for one document. Now for that purpose I use special sheet with template and just duplicate it.

Answer:
While you can't create 'template' sheets to copy from, you can use the onChange() trigger to insert the template to the new sheet as soon as it has been added.
More Information:
As per the documentation on Event Objects, there exists a changeType value in the event object that is generated when the structure of a Spreadsheet is changed:
changeType The type of change (EDIT, INSERT_ROW, INSERT_COLUMN, REMOVE_ROW, REMOVE_COLUMN, INSERT_GRID, REMOVE_GRID, FORMAT, or OTHER).
The INSERT_GRID value can be used to execute some code when a new sheet is added.
Code Example:
For this example, I will assume that you have a template sheet from which you can copy the templated information from, and for this example I will assume it is called Template.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const templateSheet = ss.getSheetByName("Template");
var sheets = ss.getSheets();
function saveSheetNames() {
var names = sheets.map(x => x.getName());
PropertiesService.getScriptProperties().setProperty("sheets", names.toString())
}
function onChange(e) {
if (e.changeType == "INSERT_GRID") {
var rangeToCopy = templateSheet.getDataRange();
var oldSheets = PropertiesService.getScriptProperties().getProperty("sheets").split(",");
var names = sheets.map(x => x.getName());
names.forEach(function(name) {
if (oldSheets.indexOf(name) == -1) {
rangeToCopy.copyTo(ss.getSheetByName(name).getRange(1, 1, rangeToCopy.getNumRows(), rangeToCopy.getNumColumns()));
PropertiesService.getScriptProperties().setProperty("sheets", names.toString());
}
});
}
}
Set up:
The first thing you need to do, after inserting this script into your script editor, is run the function saveSheetNames(). This stores the current sheet names to the script's properties so that when you add a new sheet later, the new sheet can be determined.
Next, you will need to set up an onChange installable trigger.
Follow the Edit > Current project's triggers menu item, and you will have a new page open in the G Suite Developer Hub. Click the + Add Trigger button in the bottom right and set up the trigger settings as follows:
Choose which function to run: onChange
Choose which deployment should run: Head
Select event source: From spreadsheet
Select event type: On change
And press save.
Now, when you add a new sheet with the default + button in the UI, the onChange() function will run, and copy the template sheet to the new sheet. It might take a couple of seconds, but it will be done automatically.
References:
Event Objects | Apps Script | Google Developers

Related

Creating a backup of data entered into a sheet via Google App Scripts

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.

Google sheet script - copy & plase a cell value

I need a simple script for Google sheet which will automatically daily will do simple task - copy a 3 cell's values and paste into another 3 cells as a plain text.
Is anyone can help me?
Assuming your spreadsheet page is named PAGE 1 and you want to copy the data from A1 to A3 and paste it into the same PAGE 1 in cells B1 through B3, this is the script to use:
function MyFunction() {
var ss = SpreadsheetApp.getActive();
ss.getRange('Page 1!A1:A3').copyTo(ss.getRange('Page 1!B1:B3'), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}
To achieve what you want to do. I recommend you to use the Time-driven triggers, which help you to trigger events every "n" time (mins, hours, days). Follow these steps to configure the Time-driven triggers.
1) Go to your Apps Script project
2) Click Edit->Current project's triggers
3) Click "+ Add Trigger"
4) Select :
Choose which function to run -> setTimeTrigger
Select event source->Time-driven
Select type of time based trigger->Day timer
Select time of day->[Hour you want]
5) Click Save
6) Use this code in your Apps Script project:
// Name this function as "setTimeTrigger"
function setTimeTrigger(){
// Get first sheet in the active Spreadsheet
var ss = SpreadsheetApp.getActive().getSheets()[0];
// Parameters you set
var sourceVals = "A1:A3";
var destination = "B1:B3";
// Copy and paste the values where you want to
ss.getRange(sourceVals).copyTo(ss.getRange(destination));
}
Docs
You can learn more about triggers and Spreadsheet Class in the following docs:
Installable Triggers
Class Spreadsheet

Automate dynamic IMPORTRANGE in google sheets

I am building a multi-section questionnaire (3 in total) and I want to have 4 sheets to hold the data (one master and one for each section).
How would I send data to another sheet from the master spreadsheet when a new row is added to the Master Sheet and make it dynamic so it does not pull the same row everytime?
I have found this script online:
script link
but it is for moving data between sheets unfortunately. Could it be remodeled?
Thanks!
Perhaps the setFormula class can help you, it can be dynamic & you can update it with a trigger
sheet.getRange(1,1,1,1).setFormula('=IMPORTRANGE("SPREADSHEET_URL", "SHEET_NAME!'+rangevariable1inA1notation+':'+rangevariable2inA1notation+'")');
Make sure you get the range you want, and the notation of those 2 variables to get the dynamic range:
var rangevariable1inA1notation = sheet.getRange(1,1).getA1Notation(); // for example, instead of 1,1 it could be your own variable
var rangevariable2inA1notation = sheet.getRange(2,2).getA1Notation(); //for example
To send values to another sheet (besides from your main one), you can use the onFormSubmit function, it will be triggered every time the form is submitted and with its event object, which contains the information from your form, you will be able to pass those values into the other sheets as you want.
// This will be triggered every time the form is submitted
function onFormSubmit(e) {
// Get all sheets in the active spreadsheet
var sheetsArr = SpreadsheetApp.getActiveSpreadsheet().getSheets();
// Get the second sheet
var slaveSheet1 = sheetsArr[1];
// Get the row where the values will be inserted
var rowVals = slaveSheet1.getLastRow() + 1;
// The number of cols where you will puth the values
var numberOfCols = e.values.length;
// Set values that came from the form
slaveSheet1.getRange(rowVals, 1, 1, numberOfCols).setValues([e.values]);
}
Be careful with the installable triggers Restrictions.

How to make each function run on specific page?

I have two fuctions with same name "myFunction" I run it using google trigers on edit, and I have two sheet how to specify each one for each sheet I use this but it doesn't work
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Copy of Timesheet & Feedback");
//and on the other function
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Visits");
Problem:
getSheetByName() will return the sheet you're looking for, but has nothing to do with the sheet that is being edited to trigger your function.
Solution:
Instead you can use event objects for this purpose. Wrap your whole script inside an if statement checking the name of the sheet. The new function should look something like this:
function myFunction(event) {
var ss = event.range.getSheet();
if (ss.getName() === "Copy of Timesheet & Feedback") {
//your code here
}
}
This way the code will not continue if it doesn't find the sheet it's expecting (the sheet name inside the if statement).
Notes:
Notice the name of the function myFunction(event), "(event)" is important so that we can access the event object to grab the spreadsheet that is being edited.
You won't be able to run the script manually, set up a trigger for "on Edit" and it'll just run automatically.
References:
Event Objects

create and bind onEdit script to spreadsheet from inside form-bound script

I'm having a bit of trouble with this code, this is a script bound within a Google Form and triggered on Form Submit:
function onFormSubmit(e) {
//these lines are to get the email address that was entered into the form
var familyResponses = e.response.getItemResponses();
var familyEmailAddress = familyResponses[0].getResponse();
//this loads the template spreadsheet
templateSpreadsheetFile = DriveApp.getFileById("xxxxxxxx");
//this generates an 8-digit random number
var eightDigitCode = Math.floor(Math.random() * (99999999 - 10000000 + 1)) + 10000000;
//this makes a copy of the template spreadsheet, with the 8-digit code as the name of the copy, in the specified folder.
var FamilyWorksheetsFolder = DriveApp.getFolderById("yyyyyyyy");
var newSpreadsheetFile = templateSpreadsheetFile.makeCopy(eightDigitCode, FamilyWorksheetsFolder);
newSpreadsheetFile.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.EDIT); //this allows anyone with the link to view+edit the new file
//now we need to add a script to the new spreadsheet, that makes a copy every time it is edited
ScriptApp.newTrigger('familyWorksheetScript')
.forSpreadsheet(newSpreadsheetFile)
.onEdit()
.create();
//these lines build the subject and body of the email
var emailSubject = "link to your worksheet";
var emailBody = "Here is the link to your worksheet: \n" + newSpreadsheetFile.getUrl() + "\n\n\
Your spreadsheet is named with your unique 8-digit code " + eightDigitCode
//send the email
GmailApp.sendEmail(familyEmailAddress, emailSubject, emailBody);
}
function familyWorksheetScript() {
}
==============================================
The idea here is pretty simple:
family fills out form (one question: what's your email address?) and submits
onFormSubmit script runs (installed trigger), gets email address, generates random 8-digit code, makes a copy of a template spreadsheet, the copy is named with the 8-digit code. puts it in the right folder, and sets the permissions.
then the family is emailed with a link to the spreadsheet
All the above works. But I would now like to add the feature, from within this form-bound script, to create an on-edit triggered script bound to the new spreadsheet (copy of template, named with 8-digit code). And this is the part that I can't get to work. It's the ScriptApp.newTrigger code block, when I comment it out, the whole script runs fine I get the email at the end. But when I leave in the ScriptApp.newTrigger code uncommented, the script dies right at that spot. I can tell it's dying there because the new spreadsheet still gets created, but the email doesn't get sent. I don't know why it isn't working and I don't know how to troubleshoot it.
Any help would be much appreciated. Thanks!
You can't creat a trigger on a sheet that would act as the new sheet user, everything you create belongs to you ! the triggered function would run as "you", not the new user.
What I would suggest id to create an "onOpen" function with a UI that would ask for the new user to click on a "button" to run a function that would create the onEdit trigger, asking them for explicit authorization.
Edit
below is a sample code with an onEdit trigger working on the copy of the active spreadsheet, just for test purpose.
function createNewCopy(){
var ss = SpreadsheetApp.getActive();
var newSs = DriveApp.getFileById(ss.getId()).makeCopy('copyOf-'+ss.getName());
var nSs = SpreadsheetApp.openById(newSs.getId());
var trigger = ScriptApp.newTrigger('onEdit').forSpreadsheet(nSs.getId()).onEdit().create();
}
the onEdit in the original SS is as simple as that, just to check it works :
function onEdit(){
Browser.msgBox('hello');
}
note that no trigger will be viewable in the spreadsheet's script editor ressource tab, it will only appear in your own triggered function list in your Google account.