Prevent manual copy of spreadsheet Google script - google-apps-script

I have a Google sheet that is created monthly, so have writtena script that will make a copy of the current month's sheet in a specific folder and then populate cells in the new spreadsheet with data from the old. It works well.
I am trying to come up with a solution to warn users if they try to manually copy the spreadsheet using the 'make a copy' menu item to instead use the custom menu item provided.
I thought about setting a flag in a cell, but of course that only works for the first time, as a manual copy also copies the flag. If I could detect when the file is copied manually, I could reset the flag and then test for it onOpen.
Can someone please point me in the right direction. Unfortunately the users need to be able to edit the document, so I don't think I can disable the option in the file menu.
Many thanks

You can address this specific issue:
I thought about setting a flag in a cell, but of course that only
works for the first time, as a manual copy also copies the flag.
By using PropertiesService, you can use document property to add a property to a specific document. This property can be accessed by the script, run by any user, on that specific document.
However, if you copy the document, the property is not copied. You can look for this property during onOpen and determine if the document was copied the way you want.
You can do something like this:
function setProp(){ //use this to set the property to the document
var docProp = PropertiesService.getDocumentProperties();
docProp.setProperty("Copied", "false") //Properties are stored as text immaterially of the type of value you pass to it!
}
function getProp(){ //use this to check if the property of the document was set, if set will return true, else false
var docProp = PropertiesService.getDocumentProperties();
Logger.log(docProp.getProperty("Copied"))
return docProp.getProperty("Copied") == "false"
}
function onOpen(e){
var ui = SpreadsheetApp.getUi()
if(getProp()){ // Check to see if the property was set for the sheet.
ui.alert("Copied correctly")
} else { // This will trigger each time they open the sheet,
ui.alert("Copied Incorrectly, please use custom function to make a copy")
}
}
Hope that helps!

Related

Writing in a newly created Google spreadsheet using an external script

I have built a short script that takes inputs from a Google Form and creates a new spreadsheet. But when I try to set that values inside the sheet, nothing happens. Not sure if this is due to my code or to the lack of authorization given this is a newly created file. Here is my code:
var templates = DriveApp.getFilesByName('Template'); // get the files named Template (only one)
var template = templates.next(); // get the first files in the list
var newFile = template.makeCopy(name,newFolder); // Make a copy of the Template file and put it in NewFolder
var ss = SpreadsheetApp.open(newFile);
var sheet = ss.getSheets()[0];
sheet.getActiveRange('B1').setValue(name);
Thanks for your help
I think that in your script, an error occurs at getActiveRange('B1'). Because the method getActiveRange() of Class Sheet has not arguments. Ref I think that this is the reason of your issue. In this case, an error like The parameters (String) don't match the method signature for SpreadsheetApp.Sheet.getActiveRange. occurs. I thought that the reason of nothing happens might be that from your question, the script is run by the OnSubmit event trigger might be used. In this case, please modify as follows.
From:
sheet.getActiveRange('B1').setValue(name);
To:
sheet.getRange('B1').setValue(name);
When you modify like above and run again, the value of name is put to the cell "B2" on the 1st tab in the created Spreadsheet.
Note:
If you want to append the values when the script is run, please modify sheet.getActiveRange('B1').setValue(name); as follows.
sheet.appendRow([, name]); // In this case, the value is append to the column "B".
References:
getActiveRange()
getRange(a1Notation)

Call custom function in google sheets only once and save value to cell

I have written a custom google apps script function that is being called by a google sheet. In this function an API is called and the result of this API is being returned so that it gets displayed in the calling cell of the google sheet.
My problem now is that the sheet calls this function everytime I open the document and there are over 80.000 cells with this function.
Is it possible to save the returned value to the cell and don't call the custom function again when an value has been returned? I want the function being called only once per cell, even if I close the document and reopen it. The returned value should be saved until something else is being written into to cell. That would make my sheets document much more usable than the current state.
From the question
Is it possible to save the returned value to the cell and don't call the custom function again when an value has been returned? I want the function being called only once per cell, even if I close the document and reopen it. The returned value should be saved until something else is being written into to cell. That would make my sheets document much more usable than the current state.
By solely using the custom function it's not possible. There isn't a straight forward solution is to achieve this, in order to keep things simple you should look for another way to implement what is being done by the custom funtion.
One option is to use a trigger (including a custom menu / a function assined to a drawing) to check if the cell that should have the value returned by the custom function is empty and in such case fill it with the corresponding value. The best trigger to use will depend on your spreadsheet workflow.
As specified by Ruben this is not possible, with a custom function.
In my particular case I have resorted to using an Apps Script function that is triggered by an edit event of the spreadsheet and verifies if the event is in the column where the function that I want to execute only once should be, later replacement its content with the result of calling the API.
function freezeValue(e) {
var rangeEvent = e.range;
var col = rangeEvent.getColumnIndex();
if (col === 2) { #Verify column of event
var value = rangeEvent.getValue();
/*Call your api*/
result_api = CALL_API(value)
rangeEvent.setValue(result_api);
}
}
Keep in mind that this implementation only works when each of the cells is edited one by one. To do the same with a row or a complete array of elements, you must go through each of the cells and follow the same procedure.

Apps script onedit triggered too fast causing code conflicting with itself

I'm trying to make an applet in Google Spreadsheets and Apps script that scrapes view data about youtube videos searched by users. When a user types in a search query, a new sheet should be copied from a template sheet and customized to the query. The problem I'm encountering is that when a user rapidly types in multiple queries in succession, the script would dump multiple copies of the template sheet and name them 'Copy of template 1', 'Copy of template 2' and so on, whereas the name of each sheet should be "KW: " + its associated keyword. I suspect this is because the function duplicates and renames a sheet, but if two or more instances of a function try this at nearly the same time, they target the same sheet, causing errors.
This is the culmination of what I've tried:
function main(e){
//spreadsheet=SpreadsheetApp.getActiveSpreadsheet() at the top of the function
//keyword=the string the user typed in
//...
while(true){
var random=Math.random().toString();
try{
//assign the template sheet a random name other processes will fail when they try to use it
spreadsheet.getSheetByName('template').setName(random);
//make a copy of the template
spreadsheet.getSheetByName(random).copyTo(spreadsheet);
//give the copy a proper name
spreadsheet.getSheetByName('Copy of '+random).setName("KW: "+keyword);
//reset the name of the template so other processes can use it
spreadsheet.getSheetByName(random).setName('template');
break;
}
//when a process fails, it should wait then try again
catch(e){Utilities.sleep(Math.round(random*250));}
//...
}
main is has a trigger set on edit. The above code prevents any 'Copy of template n' sheets from appearing, but it simply leaves out most of the sheets that should be produced. My guess is that the code encounters an error in the first line of the try block and loops until it times out. I'm very much at a loss as to what to do. I'd appreciate any help, thank you!
Try copying the template sheet first, then changing the name of the new sheet. Your solution runs into errors because you're modifying the template sheet, but you should really avoid doing that. With this approach, you'll never modify the template sheet.
function main(e){
//spreadsheet=SpreadsheetApp.getActiveSpreadsheet() at the top of the function
//keyword=the string the user typed in
//...
while(true){
try{
// Select the template sheet
var templateSheet = spreadsheet.getSheetByName("template");
// Set the new sheet name
var sheetName = "KW: "+keyword;
// Copy the template sheet and set name
var newSheet = templateSheet.copyTo(spreadsheet).setName(sheetName);
break;
}
//when a process fails, it should wait then try again
catch(e){Utilities.sleep(Math.round(random*250));}
//...
}
}
Granted that I don't have full knowledge of what you're doing with your script, I do have some major concerns with your approach. I don't think you should be creating a new sheet for every query. For one, that will make your data really challenging to aggregate. Secondly, since sheet names must be unique, any time someone searches for something that already has an existing sheet, your function will get stuck in the infinite while loop you set up.
I don't know what kind of data you're placing in the sheets, but you should consider trying to record the data in one sheet. Also, although your while loop works and will, in the case of errors, eventually be terminated by Google's script limitations, you should really try (1) implementing something more robust like appending a number to the sheet name and (2) properly logging the errors.

var userProperties = PropertiesService.getUserProperties(); is not working properly for me?

I am creating userproperties with PropertiesService, as
var userProperties = PropertiesService.getUserProperties();
for storing users single token for my add-on , When i set usertoken in userproperties with a key pair value as, userProperties.setProperty('USERTOKEN','token'); in an document.
Once i do this as per the userproperties scope i can retrieve the user properties value from any of the document by using userProperties.getProperty('USERTOKEN'); but,
When i do that the value is null (i.e), i cant retrieve the 'userProperties' value from other documents,
So that, the scope of the userproperties fails. The userproperties value is associated only with the particular document where it's created.
once my add-on is installed i used to check every time userproperties value for all documents ,
if(value)
{
retrieve data;
}
else
{
authorize;
}
Thus value is null and the user is made to authorize everytime for every new document. Since my add-on cant retrieve the value from userProperties.getProperty('USERTOKEN');
According to the documentation at Properties Service - Saving Data:
"Script properties can also be saved via the script editor user interface by going to File > Project properties and selecting the Project properties tab. User properties and document properties cannot be set or viewed in the user interface."
I'm not sure what it means but I think it means that even if you are able to set the property using script, the value will not update if you view it in File > Project Properties > User Properties.
But it doesn't mean that the property was not updated though. However, if it's a Script Property you set via script, the value will update in your view.
This I say because I tried it by setting it inside the doGet(). See the example below:
//Set properties as global variables
var userProperty = PropertiesService.getUserProperties();
var scriptProperty = PropertiesService.getScriptProperties();
function doGet() {
userProperty.setProperty('uservar', 'Hello');
scriptProperty.setProperty('scriptvar', 'World');
Logger.log(userProperty.getProperty('uservar'));
Logger.log(scriptProperty.getProperty('scriptvar'));
return HtmlService.createTemplateFromFile('index').evaluate();
}
I did it inside doGet() so that I can check it simply by refreshing my WebApp page.
Interestingly, the log shows the correct values (Hello, and World).
But if I visit the File > Project Properties, only the Script Propery value was updated, the User Property remains the same as the original value I set it for.
Hope this helps.
For some reason it seems that the PropertiesService does not work. Even when I use Google example in https://developers.google.com/apps-script/guides/properties#reading_data I am getting nulls. I have reverted back to the old UserProperties and it works, however it is supposed to be depreciated...
PropertiesService seem to be working for script properties though.

Google Sheets: noticing and acting upon new table added and table renamed

The title is the question.
I'm working on a spreadsheet in Google Drive where we got a central sheet named Name registry with names of the people who ever visited some place where notebooks are left at for them to write their name down on. The sheet calculates various stats, too.
The sheets for different places will be named as whatever the specific notebooks were named. The name registry saves us writing and fixing by referencing the cell of a particular name.
I'm sure there won't be anywhere like 255 sheets (which should be a lifted limit in the new Sheets), but I came across an idea how to have it automated:
1) Optionally a new sheet is added and the spreadsheet notices it.
2) Once either
2.1) the new sheet is renamed, the `Name registry` will auto-name one of the free columns as the renamed sheet; or
2.2) an old sheet is renamed, the sheet's old column is renamed in the `Name registry`
I did check on things, but the documentation is, frankly, brainlessly organized, so the only option is to use Google on its own resources.
For example, https://developers.google.com/apps-script/understanding_events implies it can't be done anyhow.
You should know that there is, indeed, a trigger that fires when inserting one sheet (I'm guessing that's what you call "table" in your question).
Re-check the docs for the spread sheet Change event, this event only fires if you use an installed trigger. To use it (copied from the docs)
Open or a create a new spreadsheet.
Click the Unsaved spreadsheet dialog box and change the name.
Choose Tools > Script Editor and write the function you want to run.
Choose Resources > Current project's triggers. You see a panel with the message No triggers set up. Click here to add one now.
Click the link.
Under Run, select the function you want executed by the trigger (for example myFunction).
Under Events, select From Spreadsheet
From the next drop-down list, select On Change.
Click Save.
Please notice that this trigger is not the same as the simple onEdit one.
Also, bear in mind that your callback function must receive the event parameter:
function myFunction(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.getSheetByName("Sheet1").getRange("A1").setValue(e.changeType);
}
You'll see that there are different types of changes: EDIT, INSERT_ROW, INSERT_COLUMN, REMOVE_ROW, REMOVE_COLUMN, INSERT_GRID, REMOVE_GRID, OTHER. You're looking for INSERT_GRID for inserting new sheet and OTHER for renaming it.
Having said that, the problem here will be detecting the rename of one sheet as its an OTHER type, so you may easily trigger the function when you don't really want to do it (for example changing background color, also fires the onChange event with OTHER edit type). My advice is that you don't try to detect the createion/change but create a custom menu entries for handling the creation and renaming of sheets:
function onOpen()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [ {name: "Create sheet", functionName: "createNewSheet"},
{name: "Rename current Sheet", functionName: "renameCurrentSheet"}
];
ss.addMenu("Custom operations", menuEntries);
}
function createNewSheet()
{
// create the new sheet
// also execute the logic you want when new sheet is created
}
function renameCurrentSheet()
{
// rename current sheet
// also execute the logic you want when sheet is renamed
}
Hope I put some light to your problem.