I have a very simple script that is triggered every 15 minutes to re-alphabetize a sheet based on that day's date. It works perfectly, except for one issue. The sheet is used by about 8-10 users at any given time. If a user is actively typing in a cell when the function is triggered and the sheet is resorted, then when they finish typing the cell they have edited the wrong cell. For example, if they start editing cell D24, and then after it is resorted that row becomes D28, then when they hit enter they will overwrite the new D24.
Is there any way I could incorporate something in the code to check if there are any cells that are actively being edited (greyed out because someone is inside it)? Or some other solution that would solve this issue?
function Alphabetize() {
var spreadsheet = SpreadsheetApp.getActive()
var now = new Date();
var name = Utilities.formatDate(now,"EST","MM-dd")
var day = now.getDay()
var sheet = spreadsheet.getSheetByName(name)
var col = sheet.getLastColumn()
var row = sheet.getLastRow()
var range = sheet.getRange(3, 1, row, col)
range.sort({column: 1, ascending: true})
}
Since modals don't work in this situation (time based triggers and other users) I figured out a slightly clunky work around. I added a row at the top of the sheet in the frozen section that is bright yellow and says "Press Enter. Sheet about to re-sort." multiple times so it's visible across the entire sheet. I then hid that row and inserted this code right before it sorts:
sheet.showRows(2)
Utilities.sleep(10000)
So it shows that row, waits for 10 seconds, sorts and then hides the row again with sheet.hideRows(2). Elegant, it is not. But it works on the time trigger and every user sees it. If there's a more elegant solution, I'm all ears.
Is there any way I could incorporate something in the code to check if there are any cells that are actively being edited (greyed out because someone is inside it)?
I think that this could be possible by using a web browser extension that reads the spreadsheet DOM but it will not very dependable as the web page source of many Google apps are generate automatically and anything could change without previous notice.
By one hand, the status identified by the web browser extension should be saved on a document / script store by using the Properties Service or the Cache Service so the time-driven trigger could read the status and by the other, the time-driven trigger save a flag to warn the user using the same services so the web browser could read them.
NOTE: In order to make this work, the web browser should call functions from the same project of the functions ran by the time-driver trigger.
By using Google Apps Script you could use spreadsheet.toast(...) to show a modeles and non so intrusive warning, then use a modal dialog to interrupt the user.
Related
I have a google spreadsheet and a randomly selected row is always shown in row 3:
A3:
=round(rand() * 808 + 1)
B3:
=VLOOKUP(A3;A8:B815;2)
When i click on a button, i want the formula to reload and also jump to that row, so i made a macro:
function Refresh() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A1').setValue("");
SpreadsheetApp.flush();
spreadsheet.getRange("A7").offset(spreadsheet.getRange('A3').getValue(),0).activate()
};
The reason, that i didnt put the calculation of random into the macro is, that i also want this random row in spreadsheets where macros are disabled (mobile + users who dont know).
When i run the macro, a new value is calculated in A3 (from flush) and then the corresponding row is activated. The issue is, that with the last row of the macro the value of A3 is recalculated because something changed and then the number does not match up anymore with the activated row.
Is there a way to block calculations? Or to scroll to the row without activating recalculations?
Tl;Dr: It's not possible to avoid the recalculation of RAND() or any other Google Sheets volatile function (NOW(), TODAY(), RANDBETWEEN()).
RAND() is a volatile function and this kind of function are recalculated every time that the spreadsheet is opened, an edit is made and optionally every certain time set in the spreadsheet settings. Also it's recalculated on the Google servers when the spreadsheet is get / opened in Google Apps Script.
You might try to use an on edit trigger instead of a "macro". Simple on edit triggers doesn't require authorization to run, installable triggers require authorization of the user who is installing the trigger, so they will be triggered by any edit made by any user but not when a macro or script are executed.
Below is an example of a simple on edit trigger. It sets the value of Sheet1!A1 to a random number.
function onEdit(e){
const value = Math.random();
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.getRange('Sheet1!A1').setValue(value);
}
Related
Does Google Sheets have "Volatile" Functions like Excel?
In addition to Ruben's answer, I want to add
Some macros like onEdit do work on mobile apps
Nevertheless, activate() doesn't work on mobile apps.
During testing, I can also see that the random numbers shown in mobile apps are completely different from what's shown on desktop for the same cell with the same formula. This number is also completely different from what is logged in apps script. This shows they're not synced properly.
I have a spreadsheet where I want users to enter information freely then "submit" their data. Once they "submit" this data I would like to protect a specific range where all other users cannot edit any data within that range with the exception of the owner; but still have the ability to view and access this data for reference.
I have tried this a couple ways and keep running into various obstacles. I am not very familiar with google script but am finding this is the only way to accomplish this automatically with little maintenance. Doing so, I have ran into two main obstacles:
The first one being the script that is being run is protecting the range based on a cell value. If the cell value of N7 is "Approved" it is supposed to protect the range. What is happening is the script is protecting the range regardless of what the cell value is (the value can be blank or a different word and it will still protect the range).
The second issue is, if I have the script run off of a "button" or recorded macro, the script will not remove the editor clicking the button. So with this issue, I (the owner) would need to go in every time to click the button to remove all editors.
The current script that I have right now is listed below. This script is to perform the protection based off the cell value in N7 on open:
function onOpen() {
var spreadsheet = SpreadsheetApp.getActive();
var approvedCellD1 = spreadsheet.getRange('N7').getValue();
var rangeD1 = spreadsheet.getRange('B5:L64');
var protectionD1 = rangeD1.protect().setDescription('Day 1 Approved');
if(approvedCellD1 == "Approved") {
protectionD1.removeEditors(protectionD1.getEditors());
protectionD1.addEditor(['myemail#gmail.com']);
if (protectionD1.canDomainEdit()) {
protectionD1.setDomainEdit(false);
}
}
};
I made a similar version of the spreadsheet it with the main components and still receive the same issues.
https://docs.google.com/spreadsheets/d/19NReUL2GSNWjMGrzvskQx86JDWox3e2wzDpk3PnKGX4/edit?usp=sharing
Please let me know if you have any suggestions or advice that I can try!! I have been searching and modifying my scripts for almost 2 months now and am still experiencing difficulties.
THANK YOU!!!!
So I have a Google sheet where, when a user enters a number of an item, it will output a description and price. I actually have 50 sheets (one for each state in the US) that are all almost exactly the same, but put out slightly different prices because state taxes vary from state to state.
I used onEdit() to have my sheet work and it was working fine until I changed where the source for information came from. Originally in my sheet, I had another page with all the item information so that a simple Vlookup could do most of the work except calculate the item's price (this is what my code was doing, using the info page that was in the sheet to calculate a price).
However, when an edit needs to be made to an item, I want to make it so that we only have to update one "master" sheet, and make a call by openByUrl(...) instead of going to all 50 sheets and copy pasting the information. I tried implementing this in a sheet, and now it doesn't work when I edit, but it does work when I manually go into script editor and press run. What gives?
EDIT: Here's the code requested.
function onEdit(d) {
itemPriceSetup();
}
// Runs the actual program.
function itemPriceSetup() {
// Grabs and stores the sheet where a customer places an item number and where the code will output the price to.
var orderSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Item Sale Doc");
var orderSheetArray = orderSheet.getSheetValues(1, 1, 34, 8);
// Grabs and stores the sheet that has the information on the item.
//***var infoSheet = SpreadsheetApp.openByUrl('link to info');
var infoSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet with info");
var infoSheetArray = infoSheet.getSheetValues(1, 1, infoSheet.getLastRow(), 10);
So the code with the three asterisks is what I want to use, but causes my the program to not work - that is, it onEdit() won't run (I have it commented out so the code will run - the line below it is the one I'm trying to replace). If I were to go through the debugger with the line un-commented, it actually works.
So I figured it out, but it's a bit strange. I didn't realize triggers for a script are found under resources, so I made put one straight in my script (I guess?). Either way, use the link and go to "Managing triggers manually" to read on how to do it.
https://developers.google.com/apps-script/guides/triggers/installable
I had the same problem onEdit(e) wasn't working. After trying out many different things disabling the new Apps Script V8 engine worked for me. To disable go to Script-menu Select Run and click disable the engine
Dissabling new Apps Script V8 engine
I hope this answer will helps many. I appreciate all of the help that I've received on StackOverflow.Thank you
I have a Google spreadsheet with an Add-on that takes data from a form and runs on form submission. I also have another Add-on that pushes the data from this spreadsheet to another spreadsheet - let's call it spreadheet2 here. In spreadsheet2 I have my own script with a function copyLastRow() that copies the last row from this spreadsheet to another spreadsheet - let's call it spreadsheet3. My script is supposed to append a new row from spreadsheet2 to spreadsheet3. It runs OK when I run it manually, but it is not running via the project trigger - which I installed for Script editor's Resources - I tried both on Edit and on Change triggers, but they are simply not firing up when data is pushed from spreadsheet2. The script is working when I actually edit spreadsheet2. However, this is not good for what I need - I really need the script to work without manual intervention. Can you, please, help?
function copyLastRow() {
var target = SpreadsheetApp.openById('xxxxxxxxx').getSheetByName('Sheet1');
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var lastrow = sheet.getLastRow();
var sourceData = sheet.getRange(lastrow, 1, 1, 53).getValues();
target.appendRow(sourceData[0]);
EDIT: I updated the code - I realized I left the previous version of code here.
You're right to worry about whether this function will be effective when multiple users are submitting forms... it won't be. But it's easily improved.
What's the problem? When copyLastRow() runs, it assumes that the last row of the source spreadsheet contains the response that also triggered the function. However, before it gets around to reading that row, another user might submit a form. (Eventually, the function will be triggered by that submission as well, and could process the same row a second time.)
The simplest improvement in this situation is to take advantage of the event object that is provided to the trigger function as a parameter. See Google Sheet Events for some background details.
The newly submitted responses are in event.values, which is an array - exactly what is needed for .appendRow(). Here's how we can update your copyLastRow function:
function copyLastRow(event) {
var target = SpreadsheetApp.openById('xxxxxxxxx').getSheetByName('Sheet1');
target.appendRow(event.values);
}
Now it doesn't matter how many users submit forms - each will be handled uniquely by this function.
I have created a form that pushes data to a Google Spreadsheet. The data is latitude, longitude, location, and other identifying data. The spreadsheet is then published as a .CSV file and imported into ARC GIS to be displayed on an interactive map. It works exactly as I wanted and I set it to republish after each change.
The problem is that when the spreadsheet has rows appended by the script, it is not seeing it as a change and republishing. In order to get the updated data imported to the map, I need to go in and manually republish. Is there anyway through the Google Apps Script that I could make a few lines of code to force a republish? I could then add that to the "on form submit" script I have or another time based one that already runs at 3 am everyday.
I have looked through the Google Apps Script documents and not found anything. When searching for help on the web, the overwhelming majority of responses are for how to publish your script as a template for other.
My testing sheet was republished after the following function was executed by either a menu entry or a time-based trigger.
function ChangeIt() {
var sheet = SpreadsheetApp.getActiveSpreadsheet()
var t = new Date()
var x = 'upd: ' + t
var range = sheet.getRange('a3')
range.setValue(x)
}
If I were in your shoes, I'd add an extra column to the end of the sheet with some benign constant data that a script can change without affecting the systems consuming the data. If an extra column isn't an option, try modifying my sample to read in a current value, change it, and immediately change it back.
Also, I'd see if the spreadsheet onEdit() trigger fires when the form submit adds a new row. If so, tie your GAS function to it to force the republish. If not, setup a timed trigger to execute the GAS function.
A quick workaround for this issue that doesn't require scripting is to simply make an array copy of the data.
For example, I made a new tab and in A1 put this: =ArrayFormula('Form Responses 1'!A1:Z1000)
While the main Form responses tab will insert rows and not play nice with formulas this new tab stay nice and constant and updates automatically when new data is added.