Background:
At my (new) job, we have a number of Sales Reps who book gigs for our organization. Until now, they handle all of their sales tasks in our CRM (Pipedrive [PD]). They then use that information to create an "activity" (event) on PD's calendar, which is set up for two-way sync with our general work Calendar (Outlook [OL]).
Problem:
Between the number of different Reps who edit the PD Calendar, the variety in bookings for our org, and just general human error, our Calendar system is a NIGHTMARE. Formatting inconsistencies, typos, lack of necessary information, and even straight up wrong details make my job (creating a program around, and then organizing the talent for each booking) nearly impossible.
"Genius" "Solution":
Even though it's a mess, I hate the type of person who comes into a new position and instantly tries to make changes to a system that has (somehow) worked before their arrival. So instead I sat down with the reps, learned what their needs were for the calendar, as well as their process, and built a system that not only gives me what I need, but also cuts the busy work on their end in half.
Previously, they not only entered into into PD, and then manually created the events, they also created three separate supplementary Word files that documented the details, created a detailed schedule of events, and outlined contractual needs based on the type of event.
So to solve all of this, I created a Spreadsheet (SS) Template that had all the fluff prefilled, was able to fill in all the contact info from an external contact database, and fill in the entire event schedule based on "type" and "start time". So basically they just need to enter in "Where", When", and "What", and the SS would auto populate the rest.
How that benefits me, is I then painstakingly scoured YouTube, Reddit, and Stack overflow for information on how to build Scripts for Google. And managed to make a function that can pull all the information from their new fancy form, and automatically create a 100% accurate and consistent Event for me.
Everybody wins, right?
New Problem:
Due to things well beyond my current knowledge, I am unable to create a dummy proof way for the event to "Trigger". I know it has to do with user permissions, and the limitations of Simple Triggers yada yada, but I'm incredibly annoyed that I managed to eliminate so much busy work, entirely solve my problem in the process, and yet the thing blocking me in the end is that I have to actively go to my Script and hit "Run" for it to properly function.
(For any "Expedition Force" readers out there, this feels a lot like how for so long Skippy could program intricate FTL Jumps across Spacetime, but still needed a "filthy monkey" to push the "jump" button)
Relevant Additional Details:
As I said, we use Office, but we're likely switching to Google down the road (and as someone who works in the arts and has been a poor student/struggling artist for the last two decades, I am much more versed with Google than Microsoft. I've also rarely found anything of value within my needs that one could do that the other couldn't, so I've built this whole system in Google Apps Script. However, if someone finds a potential solution where this will work in Excel with VBA instead, then I'd learn to translate what I've written so far.
Also, the Script works. I can make it work 100% of the time via the Apps Dashboard (where they don't worry as much about permissions), so it's not a problem with the code itself, which is why I haven't posted it here (but I can if anyone has a reason to think it's relevant).
It is a frequent need for our Reps to go in and edit the gig (people get sick, availabilities change, or mistakes were made the first time, etc.), so to combat this, I've actually written two Scripts. The first is a "Create" script which takes all the information, creates an event, and then pulls the EventID # which it pastes in a safe cell on a different tab. The second is an "Edit" script which searches for the previously created event by ID, and then makes the changes as needed.
In addition to PD and Office, we also use Monday.com and have a Zappier account. But I'd rather avoid either of those if possible since they have apparently changed software packages every other year before I got here, and we are in the middle of a search for a new Executive Director, so I'd rather not have this whole thing come crashing down because one minor part of it depended on Zappier, which our next ED cut from the budget, ya know?
Potential Solutions:
I can make the whole thing run via their Installable Triggers. However, I find them limiting:
I can do it by "Open" which creates the event every single time the SS is opened (which is both obnoxious AND useless, since the edits happen AFTER it is opened).
I can do it by "Edit" or by "Change", but again this just creates a whole new Event every time. I even tested it and made three random edits on empty cells in a useless tab, and it made three identical events to match the first (although this could be a solution for my 2nd Script...). I wish they could do onEdit of specific Cell, because THAT would be useful.
I can do it by form submission.. which means I can instead make a form that they fill out that creates the whole doc, and after they submit, it creates the doc AND event, but this limits the idea of having a different SS for every contract.
And lastly, which I really want to avoid, since I don't trust the Reps who manage to spell the same name wrong three different ways in three different places with having to open Scripts and hit "run", I could create a macro that notifies ME whenever changes are made, and then I could go in and manually hit the button. But..... Is that REALLY the best solution?
I know that's a lot, but I'm more looking for creative coding ways to solve a general problem, rather than a specific fix to a single string. Anything that could make what I want happen, without making me do annoying extra steps, or without requiring me to trust others with a keyboard, I am open to suggestions!
You would benefit from more research on triggers gerenally, onEdit in particular. It is broadly true that onEdit is triggered "when anything was edited" but the script can be written so that it evaluates specific rows &/or columns &/or cells &/or sheets &/or values. It can do this by using Event Objects which involve including an argument (often the letter "e", or the word "event"). The Event objects provide a lot of information about the nature of the edit.
For example, if you had a checkbox in sheet "Sheet2", column D, and you wanted to trigger something if the checkbox value was changed to "checked", then an onEdit(e) script might include an IF statement such as:
if(e.range.getSheet().getName() == "Sheet2" && e.range.columnStart == 4 && e.value == true){//do stuff}
In the scenario described, an automated trigger might not be necessary - a user might select a row (or a cell in a row), and click the "Button" in order to execute the script. However, it might be desirable to "check" a checkbox so that there is no doubt about that row has been calendarized or not. The advantage of this is that an event is not calendarized twice.
The following script might be an appropriate example:
function buttonTrigger() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var sheetName = "Sheet1"
var sheet = ss.getSheetByName(sheetName)
// get the row to be calendarized
var row = sheet.getCurrentCell().getRow()
// Logger.log("DEBUG: the cursor is on row is "+row)
// check is this row is already processed
if (sheet.getRange(row,4).getValue == true){
// this row already processed
// insert abend code
return
}
else{
// this row is OK to update
// insert script to calendarize event
// update checkbox to show event is updated
// checkbox is in Column 4
sheet.getRange(row,4).setValue("true")
}
}
Example
Creating a button and assigning a script
There is a good explanation of this process in StackOverflow topic Run script only on click on button instead with open the sheets
Related
I have two Google spreadsheets, A and B: A gets replaced every day when a scheduled report copies over it and B remains the same (and is the one I want to update).
On a scheduled basis, I want to take email addresses that appear in A and see if they also exist in B. If they don't, then I want them added to a new row in B.
I have tried coming up with a way to automate this - either through macros or Zapier and I'm coming up short. All I want to know is if this is possible and what the best way to accomplish this would be. (I'm thinking of paying a contractor to do this, but I don't really know what I'd be asking for at this stage.)
You can create a script in one of your sheet,
In your sheet open the script editor, by clicking on tool > Script editor.
create a function let's say :
function saveAtoB(e){
}
then on the left menu you have some clock icon, where you can add triggers.
click on add trigger button (right bottom).
select your saveAtoB function,
select HEAD
select trigger on time
select daily.
then save.
The automation part is Done, quite easy.
Now it is quite easy to write the code inside the saveAtoB function, if your are familiar to code in JavaScript. Else it could be little tricky.
If you share a copy of your sheet I might help.
(I suggest not to pay a contractor to do this since it probably would take less that one hour).
I'm managing a student registration system based on Google docs. Students enter their registration data in a Google Form, the results are gathered in a Google spreadsheet ("Préinscriptions") that uses additional sheets to normalize the data. The data is then imported by another spreadsheet ("Inscriptions") to handle the registrations.
In two normalization sheets in the Google spreadsheet, I use custom Apps Script functions to normalize the data from the response sheets. The functions are used in a vertical concatenation array to gather normalized data from several response sheets into the normalization sheets. The formulas look like this:
On tab "Nouveaux":
={normalizeNouveau("FR", 'FR - Nouveau'!$A$1:$AB);normalizeNouveau("EN", 'EN - Nouveau'!$A$1:$AB); normalizeNouveau("ES", 'ES - Nouveau'!$A$1:$AB)}
On tab "Anciens":
={normalizeAncien("FR", 'FR - Ancien'!$A$1:$AB); normalizeAncien("EN", 'EN - Ancien'!$A$1:$AB); normalizeAncien("ES", 'ES - Ancien'!$A$1:$AB)}
normalizeNouveau and normalizeAncien are the custom functions that normlized data linewise. "FR - Nouveau", "EN - Nouveau" ... "ES - Ancien" are the response sheets.
Now the problem is that, while this all globally works great, the formulas that gather the normalized data, on both "Anciens" and "Nouveaux" sheets, sometimes get "disconnected" (for lack of a better word) and display error messages for each formula -- either a single one or 3, corresponding I expect to the three blocks of data produced by the various function calls. When that happens (usually several times a day), what I need to do is (simply :-| ) to cut the formulas from their original cells, validate the change, then paste them again. And then the data reappears normally.
When the problem happens:
After cut-pasting:
That is very problematic because, as I said, another file ("Inscriptions") uses that data to handle many aspects of the registration process, and when this happens, that whole files becomes empty and useless -- this can even lead to errors if the problem occurs at a time where I'm running an Apps Script function, because the data is then effectively absent. So when that happens in "Inscriptions", I need to go back to "Préinscriptions" to perform the little trick I was just describing. Doing it myself is annoying enough, but other people might need to use that file too and would simply get stuck or, worse, break something if the data simply disappears without warning.
Sometimes the connection seems to break between "Préinscriptions" and "Inscriptions", which loads the data from yet a third tab where "Nouveaux" and "Anciens" get merged. The same trick applies, although it may need several attempts to successfully show the data:
Can anyone tell me why this happen and if there's anything I can do to prevent it? I'm heavily relying on the stable dataflow between the forms, response sheets and registration spreadsheet for the system to work, and this is just breaking the workflow way too often to be negligible.
I have been playing around with Google Script Editor and I've gotten heaps of use out of it.
The next task that I am looking at is automatic formatting and text insertion/replacement when copying a Template.
An example use case is as follows: Within my organisation I have submitted a Doc to the template gallery. When creating a copy of the template I want it to automatically insert today's date and the current time (rounded to the nearest hour).
This is a question about the Triggers. The text replacement bit is easy and done. Not to mention this is just one of the basic use cases, I'll be attempting many more similar behaviours with things like timesheets and the like.
The problem that I am running into is that I can't seem to get the triggers to work as I'd like them to.
2 of the Triggers that I thought I could try and use: onOpen(e) and onInstall(e).
onOpen(e), though it works, it works "too well". That is, it also replaces the text on the original template as well, proving a nuisance when updating info in these templates.
onInstall(e), I thought this would work as creating a copy of the Doc also "installs" the script as well. However this function doesn't seem to run at all.
Any ideas about getting a trigger to happen once and only once when a Doc is created from a template?
Cheers,
Bricktron
First of all, Trigger onInstall(e) works only for Add-ons.
Now coming to onOpen(e), in my opinion you can use Google Apps Script Property's Services to store one flag which helps your code identifying whether this file has been opened or not.
So for very first time onOpen(e) runs, assign property eg: propertyService.setProperty("opened","TRUE") and next time you can check by accessing the property whether it has been already "opened" or not.
Example:
var openedFlag=propertyService.getProperty("opened");
if(openedFlag=="TRUE"){
//Document has been modified
//Do not run the modifiable code again
}else {
//First time
//Edit the file
//Set the propertyService to "TRUE"
}
I have a script that creates additional files upon submission of a Google Form. It also sets up a trigger for said new document, so that people are emailed when any edits are made to the document after its creation, e.g.
ScriptApp.newTrigger("sendEmailOnModification")
.forSpreadsheet(SpreadsheetApp.openById(fileId))
.onEdit()
.create();
The logic all works, but... the trigger is extremely sensitive. Every time someone makes a single keystroke in the document, it fires. It doesn't make a lot of sense to repeatedly fire as someone writes a paragraph; it makes more sense to, say, fire 10 minutes after the last edit. (Or something. Anything that doesn't spam emails.)
Is there a way to modify the trigger so that it fires less often (I do not want a time-based trigger), or otherwise programmatically change how frequently Google Sheets saves?
As a last resort, I suppose I could create a hidden sheet that captures last modified time and a condition not to send an email if subsequent modifications are less than X minutes ago, but that's kind of clunky and not really appealing. Hoping I can do it entirely in Google Scripts instead.
You could use the Properties Service to save a timestamp of the last time that the email was sent and compare it to the actual time of the current execution.
I'm not sure how you could implement this to suit your needs,but since you don't want a time based trigger, and you are already using onEdit(), you should be able to use
onEdit(e) and e.oldValue to check if the change is worth the message.
When using Google Spreadsheets, and you want to use Validation on a cell based on a range of values, you get a pretty nice autocompletion feature that makes data entry much nicer.
My most common application is in an inventory-like situation, where I reference inventory items through some kind of hash code or part number. My frequently used hashes are committed to my brain, but when i need a new part or I need a variation on an old one, I want a little help making sure I have the correct part# selected.
I always find that I want additional row context with my autocompletion, so now I think I want to make a sidebar addon that has smarter searching rules and also includes more contextual data to ensure that I have the part# I meant. Once I am sure of the part#, one button can push the selected result over to the currently active row.
This solution is a bit "heavier" than data validation, but it does exactly what I want.
Assuming that my inventory source is another spreadsheet, what is a good way to set up my Addon-Script Project?
I was thinking that my sidebar would call an HtmlService function that utilizes Cache Service to hold my "hash list" and a few bits of context in memory. I don't think I am looking at a heavy jQuery solution (only to build the autocomplete dialog as I type), but that is really the whole purpose of this question!
Any thoughts on high level project overview? I am fairly new to Apps Scripts in general, especially since the newer API's have been coming out since 2013.
I did exactly that with my Budget Sheets, moved from Data Validation to Jquery's Autocomplete in a sidebar when the number of compositions jumped from 500 to 2.500, and it is a LOT faster, the search is faster than Autovalidation with 100 itens, the logic I use:
Database:
It's base data is in a Spreadsheet, each time it is updated, there's an OnEdit function that will trigger in several minutes a DB update, this is so that the function won't run unecesserary on several times consectively for the same edit.
The DB is then stored in simple text in JSON format on Google Drive, it is a 2MB file generated from the Spreadsheet data, using DriveApp.getFileById(id).setContet(JSON.stringify(myJsonDataFromSpreadsheet)), the file generation and saving takes up to 30 seconds, the file reading is around 4segs.
Sidebar:
Build a normal HTML - remember to use IFRAME option - and serve it, this is all in the docs, you'll have to send data from the HTML to GoogleScript (eg. the part# to insert) via google.script.run, and get back data (eg. the file with all the part numbers) with SuccessHandler in conjunction with google.script.run.
Main function references:
https://developers.google.com/apps-script/guides/html/reference/run
https://developers.google.com/apps-script/guides/dialogs#custom_sidebars
https://developers.google.com/apps-script/reference/drive/file -> First get the file With DriveApp.getFileById(id).