I have a lot of similar spreadsheets in GoogleDocs. There's an onEdit trigger in each of them that does some complex validation. Its code is the same for each document, and sometimes I need to update it for all the spreadsheets at one time.
This is what I already tried:
Install a trigger from one "mother" spreadsheet, i.e.
ScriptApp.newTrigger('f_proc').forSpreadsheet(iCurSh).onEdit().create();
This gives us ability to updatability, but also lots of collisions when executed by several users simultaneously.
Calling the validation method from library. This gives no collisions, but in case of update I have to change library version manually in each spreadsheet.
Are there any other ways?
Thanks in advance!
As one comment says above, use libraries in development mode so they always use the latest code without worrying about versions. You can also keep the code somewhere else like a google doc, get it with documentApp and inject it programmatically with 'eval' but might be slower than eval even if you cache it with cache service (I used to do this before gas had libraries)
Related
I am building an application that will have many users, each of whom will have many Google documents. Each doc will have a custom menu and that custom menu will invoke a library script. I may need or want to change the coding in that library script from time to time.
As changes to a library script must be "saved" as a new version in order for the changed version to be passed on to client scripts (in my case, the scripts bound to Google Docs), I need a way that users can "batch" update the version number in their docs' bound script appsscript.json
file.
I have researched this issue and there seems to be two general alternatives: set the client scripts' library mode to "Developmental" or use an add-on.
The problem with the former is that it won't work unless the users are all granted edit mode access to the library script (which seems particularly a bad idea as the users may well not even be known to me).
The problem with the later is essentially complication and cost. If I make the add-on private, it only works for users in the same domain which means I have to create a G-Suite domain (and pay at least (as of this writing) $72 per year per user—a non-starter for this project).
If I make the add-on public, in addition to the complication, I have to sign up to the Google Cloud Platform and the costs for that require one to navigate a veritable maze of choices and alternatives such that at this point, I really have no idea what the cost per service or user would be.
Below I present some "mock-up" code that should at least indicate the direction I am trying to go.
function upDate() {
var version = 23
var scripts = "https://script.google.com/u/0/home"
//while (scripts.hasNext()) {
//var script = files.next();
//Note: All of the script's have the same name as they commence life bound to a template, which template is duplicated to create the rest of the user's docs
if( scriptName = ScriptName){
//set.dependencies.enabledAdvancedServices[].version
}
}
I don't even know if it's possible to step through bound scripts the way one step's through files in a Google Drive, so that is the first question. Then, the second question is whether, assuming you can step through the scripts one by one, you can change a manifest value—in this case, the version number.
One cannot step through container-bound scripts as they are (no longer) located in one's Google Drive. Moreover, despite Google's documentation about using a "stable" value in the version section of the manifest, that documentation appears erroneous. Finally, one cannot programmatically edit standalone scripts.
However, there is a workaround. What I ended up doing was writing a script that steps through all of the involved Google Docs and copies them to a blank template (i.e., in effect, duplicates them all). That blank template has the bound script installed in it with the new version number of the library. Then, delete original docs (via the same script) and voilà, batch update to all of the target docs is accomplished. (One drawback of this is: if Google Doc revision history is important to you, be advised this gambit jettisons that (unless you keep the original versions).
I have read all Google documentation on managing and creating libraries, yet I still do not know if they are an appropriate option for the problem I am trying to solve.
I know how to save a version of a standalone script. I know how to add the library to a spreadsheet via the script editor. But I don't understand, very simply, how to trigger the library script within the new spreadsheet.
I have a spreadsheet that serves as an often-copied template within my organization. The template contains a script that (onOpen) accesses data on a separate spreadsheet (a master database) and sets those values on a tab called "admin." The desired result is to have a copy of the master database living within the template sheet (and every subsequent copy of the template sheet). At this point, there are thousands of copies of the template sheet, each running that same script.
Whenever I have to change the script, I have to change it within thousands of sheets. Can I use a library instead? I'd like to be able to create a new version of the script in the library and have all sheets connected to that library experience the change. I understand that the library needs to be in development mode (within each sheet) to do this. I also understand that in order to make this switch, I will probably still have to go into each sheet to add the library. I'm just hoping it will be the last time I have to do such a tedious task.
Any advice or links to solid info is appreciated.
besides making an add-on (already covered in another answer) I will answer your libraries question. They will work for you. What you are missing is the "connect" part.
For this you want to trigger the library code from say, onOpen. The onOpen in the library is not enough and not detected by apps script. Instead each of your spreadsheet's script needs an onOpen(e) which just calls yourlibrary.onOpen(e).
since those "hook" calls rarely change, specially once you stabilize your library api, and using it in "development" mode will let you modify just the library.
whenever one of those hooks needs to change (say a callback from an html GUI needs a new parameter) you need to update all the spreadsheets. to avoid this, make all your callbacks receive a single json object instead of multiple parameters.
Sorry if I am repeating other answers, but I would like to sum up and add something:
You can access your library functions as follows:
From the app using the library you go to the Resources/Libraries. You can see the library name under "Identifier". On the same line where you can select Development mode.
Library name found in resources
Now in your library you have for example a function
function onOpen(e)
{
Browser.msgBox("HELLO!");
}
In the spreadsheet app you wish to access it you use the library name found in the resources, for example "testlibrary"
function onOpen(e)
{
testlibrary.onOpen(e);
}
Now if you have development mode on, the modifications to functions in the library update automatically to your application (spreadsheet) as long as the user using the application has edit access in your library script.
If anyone using your spreadsheet has a restricted access to your library script (meaning only view access) or development selection is off in the application, you have to go to the application's script, Resources/Libraries and select the most recent version of your library to be used in the app everytime you update the library and save a new version of it.
Still, especially if you are using mostly only the onOpen function , I would recommend using the library rather than copy-pasting the function to the script of each spreadsheet, as it is easier to track which scripts are up to date and it is easier to avoid errors and differences between the scripts.
Even in the more restricted case, if you update function in library - as long as you are already calling it in the app - all you have to do is select the new version of the library used.
I hope I had anything to give in this conversation and my language was appropriate, this was my first answer..
A good question Melly. I've been through a bunch of the documentation and some tutorials on this subject but haven't tried adding any libraries yet. My understanding is that once you are connected to a library all you have to do is call the applicable functions. That once a library is connected the functions in the library become an extension of all the other Apps Script classes available in the script editor.
How do I obtain the triggers for another document in my account?
There are two functions to obtain a list of triggers:
ScriptApp.getProjectTriggers()
ScriptApp.getUserTriggers(document|form|spreadsheet)
The first returns only the triggers for the current project (as expected), but the second is always returning an empty array, except when called from the project where the trigger resides.
There is currently a bug in the ScriptApp.getUserTriggers() function (or its documentation).
What is happening is not that empty arrays are always returned. Rather, the getUserTriggers() function is returning the triggers associated with the calling script, rather than the script bound to the specified document|spreadsheet|form. If the calling script has no triggers, getUserTriggers() will return an empty array.
The issue has been taken up by the Apps Script team. In the meantime, it is not possible to programmatically view or manipulate triggers of a script from another script.
Not a perfect solution but I've listed this issue on the script issues page at https://code.google.com/p/google-apps-script-issues/issues/detail?id=4562&thanks=4562&ts=1416775997, as no-one seems to know the answer, so we'll need to wait for the Google team to clarify.
Please star it so that it gains visibility and traction.
Edit: As per Ryans mention below, the issue I listed has been picked up the the Apps script team (Based on the owner being 'ryanr...#google.com', I suspect it's Ryan himself who has picked it up, Thanks Ryan!). So currently the only solution is to wait for a fix I believe.
I will be maintaining Google spreadsheets for 100 different schools. Each school's spreadsheet will have similar functions (e.g., jumping to a certain cell based on the date or creating a note in a certain cell based on inputted text) and multiple people will have access to edit it (e.g., principal, teachers, coaches, etc). I imagine that creating a library is easiest for this purpose since the functions are the same for each spreadsheet! For example, the script file for each document uses the onOpen() event to call .addMenu(). Then .addMenu calls a wrapper function and this function calls the "jumpToToday" method in my library. (I saw the posts about how the library method cannot be called directly from the menu, so that is why I did it this way). I have two questions:
When I (the owner) share the spreadsheet with an editor, the menu shows up, but when the editor selects one of the menu options, the script returns the error:
You do not have access to library ExternalSupport, used by your script, or it has been deleted.
After I saw this, I shared the library file (Can View) with the spreadsheet editor and then it worked. Do I have to share the library (view only) with ALL editors of the spreadsheet so that they can run the functions? If so, that's fine (I was just trying to avoid it because most principals/teachers/coaches are not techie, so I didn't want to confuse them by sharing a file of code...I just want them to use the spreadsheet).
Because the editors of the spreadsheet will only be able to view the library, it seems like they only have access to the Version that is selected (even if dev mode is on).
When I update my library to another version, do I have to go into the Script Editor of all 100 spreadsheets and then go to Resources > Manage Libraries and update the version? Or is there a way to make all files use the latest version of the library?
1) Yes. According to the documentation
you must grant at least a read-level access to your project for all potential users.
I agree that this may be confusing for other users but at the moment it is necessary.
2) There is no way to make a file use the latest version of a library. Though, there is a way to make a file use the development version of a library. For this to work you need to grant editor-level access to your library to the user. However, bear in mind that any changes that you make to the library will be reflected immediately at the user's end. You can find more information in the documentation.
I have been working quite a lot with Google Apps Script lately, but there is one thing that still is very unclear to me and the docs do not hint about it at all:
When publishing a script as a WebApp (access: Anyone, as: User accessing the WebApp) and asking for permissions (e.g. GMailApp access, UserProperties and Trigger) and then afterwards changing that script (but not asking for any additional permissions, just changing code) and publishing it again, it seems as if triggers being run by Scripts priorly authorized by users lose their authorization (e.g. the user gets an email with a failure message: Authorization is required to perform that action. from that script).
I read about libraries being independent based on their version, but accessing GMailApp from within a library or a Trigger within a library is not possible as it needs the active user? Is there any way around this? What is the suggested upgrade path, e.g. how can I make (code) changes to the script without making it fail for existing users?
Some services - gmailApp and mailApp for sure- are considered as sensitive matters by Google and therefor any modification in the code, even a very minor change, implies a renewal of the authorization. I can't remember exactly right now where I read that info but I'm pretty sure I read it (!) and I saw it also as a Googler answer somewhere in this forum. Forgive me for not being accurate concerning references.
Anyway ... that explains why you have these authorization issues with your script and AFAIK there is no way to avoid this process.
That said, your users should get an authorization screen, not an error message for services that they use in your app.
If you use triggers in your app (that you set yourself of course) then you should run these functions yourself manually to pass the authorization since the triggers are executed under the authority of the one that creates them, no matter how your webapp is published.
I hope I'm clear enough, if not refer to the doc about installable triggers and this doc also.