Google Apps Script Lockservice Not Working - google-apps-script

I am calling a sidebar in a Google Sheets bound script. I am trying to prevent multiple users from opening the sidebar at a time. The code below is how I am attempting to achieve this:
function loadM1DocsSideBar() {
var lock = LockService.getScriptLock();
lock.tryLock(0);
if (!lock.hasLock()) {
SpreadsheetApp.getUi().alert("WARNING! Function currently in use by another user. Please try again later.");
return;
}
Logger.log(lock);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName("Metadata");
var dataRange = sh.getRange("metadataRecord").clearContent();
var form = HtmlService.createHtmlOutputFromFile('M1DocsConfiguratorSidebar').setTitle('Verification Project Library Creator');
SpreadsheetApp.getUi().showSidebar(form);
lock.releaseLock();
}
During testing both first and second users can launch the sidebar at the same time. Can anyone enlighten me where I am going wrong.

Issue:
The script becomes inaccessible only during the brief time the first execution hasn't finished. After that, other users can execute this, even if the sidebar is opened by user #1: the script has already ended execution. The fact that a certain user has the sidebar opened is not registered by your script.
Workaround:
A possible workaround would be using Properties Service to set and retrieve information about whether the sidebar is open, instead of using LockService. The idea would be the following:
When your main function starts, check whether there's a script property SIDEBAR_OPEN equal to true (see getProperty). If that's the case, show your alert and stop the execution. This would be parallel to your current tryLock and hasLock sections.
If the script property is not present, or not equal to true, that means no-one else has opened the sidebar. Your script can now open the sidebar, and set the script property SIDEBAR_OPEN to true (see setProperty.
On your sidebar, have a button that will close the sidebar, and which will as well call a function (setClosedSidebar in the sample below) that will set SIDEBAR_OPEN to false.
Code sample:
function loadM1DocsSideBar() {
var sidebarOpen = isSidebarOpen();
if (sidebarOpen) {
SpreadsheetApp.getUi().alert("WARNING! Function currently in use by another user. Please try again later.");
return;
}
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName("Metadata");
var dataRange = sh.getRange("metadataRecord").clearContent();
var form = HtmlService.createHtmlOutputFromFile('M1DocsConfiguratorSidebar').setTitle('Verification Project Library Creator');
setOpenedSidebar();
SpreadsheetApp.getUi().showSidebar(form);
}
function setOpenedSidebar() {
var props = PropertiesService.getScriptProperties();
props.setProperty("SIDEBAR_OPEN", "true");
}
function setClosedSidebar() { // Call me when closing the sidebar
var props = PropertiesService.getScriptProperties();
props.setProperty("SIDEBAR_OPEN", "false");
}
function isSidebarOpen() {
var props = PropertiesService.getScriptProperties();
return JSON.parse(props.getProperty("SIDEBAR_OPEN"));
}
Note:
As a downside to this workaround, this will only work if the sidebar is closed through the button, not by clicking the closing icon nor by refreshing the tab. You should probably set a timeout anyway via Utilities.sleep, so that the sidebar becomes accessible to other users after some time. Be careful with this.
Alternatively, you could use Utilities.sleep to keep the script running for some time after displaying the sidebar, so that other users cannot open the sidebar right after the first user did that.

Related

Gapps Script in Google Sheet Is Not Scrolling No Matter What I Do

I am having troubles make Gapps Script scroll the window.
Basically, I want to send the user to the first non blank row from the buttom. My sheet has around 24000 rows. The first non blank row from the bottom is on 23500 row.
I'm using this script but not matter how many time I flush, nothing happens.
I am correctly getting the address of the lastFilledRow... It's just the sheet which never scrolls.
My code is:
function go_to_last_row() {
var ss = SpreadsheetApp.openById('abc.....')
var sheet = ss.getSheetByName('All Leads')
var lastRow = sheet.getMaxRows()
Logger.log(lastRow)
sheet.getRange('A'+lastRow).activate()
SpreadsheetApp.flush()
var lastFilledRow = sheet.getActiveCell().getNextDataCell(SpreadsheetApp.Direction.UP).activate().getA1Notation()
SpreadsheetApp.flush()
Utilities.sleep(3000)
sheet.setActiveRange(sheet.getRange(lastFilledRow)).setValue('hello')
SpreadsheetApp.flush()
Logger.log(lastFilledRow)
Logger.log('done')
}
Any ideas how to force the window to actually scroll? I even set the value "hello" correctly, but still, the cursor never goes to that cell....
Use SpreadsheetApp.getActive(). For some reason, SpreadsheetApp.openById() doesn't provide the same privileged context that container-bound scripts afford. This is actually documented by Google, but in a relatively cryptic way
Functions that are run in the context of a spreadsheet can get a reference to the corresponding Spreadsheet object by calling this function.
As a result of your question, I now interpret that to mean that the other open methods (those that don't include "Active") will not give you that reference to the corresponding Spreadsheet.
This is an abridged version of your code. Works with getActive(), but not openById().
function go_to_last_row() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('All Leads');
var lastRow = sheet.getMaxRows();
sheet.getRange('A'+lastRow).activate();
}

Writing into another Spreadsheet with SetValue fails without errors/exceptions

I'm trying to update a spreadsheet from a script running on another spreadsheet.
Nothing seems to have any effect on the table (SetValue(), SetBackgroundRGB(), etc.).
I've checked the scope, it includes "https://www.googleapis.com/auth/spreadsheets" permission; besides, this same script has no problem writing to another spreadsheet that it creates in runtime.
function updateAnotheSpreadsheet() {
var targetSpreadsheet = SpreadsheetApp.openById('<target spreadsheet id>');
var sheet = targetSpreadsheet.getSheetByName('<sheet name>');
Browser.msgBox(sheet.getRange("A1").getValue()); // Here I see that my getSheetByName worked
sheet.getRange("A1").setValue('Test value'); // But this does nothing
}
There are no errors but also no effect: nothing changes in the target spreadsheet.
Hi I was testing and was able to do what you are trying to do. You can try to declare a variable for the message, here is what I was able to do, I hope this resolves your inquiry.
function updateAnotherSpreadsheet() {
//Opening the second Spreadsheet by ID
var targetSpreadSheet = SpreadsheetApp.openById("<SpreadSheetId>");
var sheet = targetSpreadSheet.getSheetByName("<SheetName>");
//Getting the range to show on msgBox.
var msg = sheet.getRange("A1:A1").getValue();
//Displaying old data on A1:A1 from the secondary Spreadsheet.
Browser.msgBox("Old data on secondary spreadsheet: " + msg);
//Getting the range and setting new values
sheet.getRange("A1:A1").setValue('Test value').setBackground("teal").setFontColor("white");
}
I would suggest to check for more information here https://developers.google.com/apps-script/guides/sheets.
I hope this helps, greetings.
I found the problem.
A function called from OnEdit() can't ask for permissions. I needed to first update that other spreadsheet from any function called by something "active", like a button; then, after the script asks for permission once, it can do its thing from OnEdit() the next time it runs.

Google Docs Custom Refresh Script

I'm trying to write a script that allows me to execute commands as soon as Google finishes making calculations (i.e. I'm trying to add to a script to Google docs that imitates some VBA "Calculate" functionalities).
The script is conceived to work by converting a range into a string and looking for the substring "Loading..." (or "#VALUE!" or "#N/A") in that string. The "while" loop is supposed to sleep until the unwanted substrings are no longer found in the string.
I'm using the following spreadsheet as a sandbox, and the code seems to work okay in the sandbox just searching for "Loading...":
https://docs.google.com/spreadsheet/ccc?key=0AkK50_KKCI_pdHJvQXdnTmpiOWM4Rk5PV2k5OUNudVE#gid=0
In other contexts, however, I have cells whose values may return as "#VALUE!" or "#N/A" for reasons other than the fact that Google is still loading/thinking/calculating. What's the way around this?
function onEdit() {
Refresh();
};
function Refresh () {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2");
// set the range wherever you want to make sure loading is done
var range = sheet.getRange('A:A')
var values = range.getValues();
var string = values.toString();
var loading = "Loading";
do
{
var randomWait = Math.floor(Math.random()*100+50);
Utilities.sleep(randomWait);
}
while (string.search(loading) ==! null);
range.copyTo(sheet2.getRange('A1'), {contentsOnly:true});
customMsgBox();
};
function customMsgBox() {
Browser.msgBox("Data refreshed.");
};
rather than using a while loop to "sleep" you should add an event handler to your document which captures the update/refresh event and then runs whatever math/processing you need.
Here's a good place to start reading about events: https://developers.google.com/apps-script/understanding_events
but if you search the api documents for eventhandler you can get some example code fast...

Improve my script run time

Is there anyway to improve my script run time? I have a script that creates 2 listboxes: Listbox1 items is all my google site pages, listbox2 items is the sub-pages of listbox1 page. The script runs fine but sometimes it takes between 2 and 5 seconds to get all of the listbox2 items.
You can try my script here.
And here is my script:
function doGet()
{
var app = UiApp.createApplication();
//GUI with 2 listbox
//Listbox1: onclick > lbox1onclick(e), onchange > lbox1onchange(e)
app.add(app.loadComponent("MyUrlParser"));
var lbox1 = app.getElementById('ListBox1');
lbox1.addItem(' ');
var lbox1_Item = SitesApp.getSite('phichdaica').getChildByName('manga').getChildren();
for(var i = lbox1_Item.length-1; i >= 0; i--)
{
lbox1.addItem(lbox1_Item[i].getTitle());
}
return app;
}
function lbox1onclick(e)
{
var app = UiApp.getActiveApplication();
var lbox2 = app.getElementById('ListBox2');
lbox2.clear();
return app;
}
function lbox1onchange(e)
{
var app = UiApp.getActiveApplication();
// var value = e.parameter.lbox1;
var lbox1value = e.parameter.ListBox1;
var lbox2 = app.getElementById('ListBox2');
var lbox2_Item = SitesApp.getSite('phichdaica').getChildByName('manga').getChildByName(lbox1value).getChildren();
for(var i=lbox2_Item.length-1; i >= 0; i--)
{
lbox2.addItem(lbox2_Item[i].getTitle());
}
return app;
}
I don't think it will speed up the process but you could use just one handler function to do that : on change listBox1, clear listBox 2 and re-populate it immediately. What is taking some time is the call to site's content so the difference might not be significative but the 'logic' of your script would be improved ;-)
Looking at your page, I see that listBox 2 is never cleared... is that a temporary issue ? Did you change something recently ?
Also, what is supposed to happen when something is selected in listBox2 ?
EDIT : following your comment, if you want to improve user experience concerning the 'responsiveness' of your UI the best way is to use client handlers to trigger the visibility of a 'wait message' for example(something like "Updating the list"). I usually use an animated gif that I made visible with a client handler and that is made invisible again when the server handler returns (ie I set it invisible in the server handler function).
here is a working example, just try to change the date in the upper right corner.

Can I test if a script is authorised and call the Google authorisation dialog if not?

My users are going to have to authorise script in new spreadsheets every 4 weeks. I'd like to call the authorisation dialog as soon as they open a spreadsheet for the first time and NOT call it if the script has already been authorised.
At present, the simplest way I've found to do this is to get them to click a "Go to Main Sheet" button (i.e. a drawing with an attached script) on a blank sheet. If they've already authorised they get taken straight to Main, if not it triggers the authorisation notice - in which case they end up having to click the "Go to Main Sheet" button a second time after they've authorised.
I don't like it, it's clunky. Is there a way to test if the script needs authorising and call the dialog directly, e.g. from an onOpen event?
This is a possible solution for single user to make authorization process simple and obvious.
I know it's not going to meet your use case since you have multiple users on the same spreadsheet but it can have parts that you could be interested in.
Here is the code and the example SS (make a copy of it to get edit access and don't forget it works only one time! (at least as far as the authorization is concerned, you can always undo the sheet deletion !!))
function onOpen(){
if(SpreadsheetApp.getActiveSpreadsheet().getNumSheets()==1){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [{name: "test", functionName: "test"},
];
ss.addMenu("Recherche",menuEntries);// custom menu
}
} // menu appears only if authorization has been processed
function test(){
if(SpreadsheetApp.getActiveSpreadsheet().getNumSheets()==2){
var ss = SpreadsheetApp.getActiveSpreadsheet();
SpreadsheetApp.setActiveSheet(ss.getSheets()[0]);// make first sheet active to be sure
var del = ss.deleteActiveSheet();
onOpen;
}
Logger.log(findThelastRow('d',0))
}
//
//
function findThelastRow(column,index){
var doc = SpreadsheetApp.getActiveSpreadsheet();
var lastRow = doc.getSheets()[index].getLastRow()+1;// check last row on the appropriate sheet
var coldata = doc.getSheets()[index].getRange(column+2+':'+column+lastRow).getValues();// I begin on Row 2 just as you did
for(i=coldata.length-1;i>=0;i--){
if(coldata[i][0]!=''){return i+2;break}
}
return 0;
}
NOTE the functions I have put in this example are just examples !
The important thing is the part that deletes first sheet when authorization is done. Hoping this will be clear enough ;-)