Count the Occurrence of a Selection of a Cell in Google Sheets with Apps Script

A mate reached out to me last week asking if there was a way to monitor the times any article in a Google Sheet list goes through an editing stage before being published. From there, he wanted to report the number of edits on the article in an adjacent cell.

Take a look at the example:

As you can see in the example, whenever “NEEDS EDIT” is selected from the dropdown menu, then the counter column for the selected row item is updated automatically.

In this tutorial, we will cover how to create this for a single Google Sheet tab or for multiple selected tabs. I’ll also wrap up with a script to calculate just the aggregate count of changes to all the rows in the selected range and report it in a single cell.

Check out the video tutorial that covers the basics and grab the Starter Sheet to play along:

The Video

Scheduled release 6 Dec 2022 9am GMT+10

Generally better to view it from YouTube.

Starter Sheet

Here is a link to the starter sheet copy the sheet and head to Extensions > Apps Script in the menu.

Starter Sheet

The onEdit(e) Simple Trigger

Everything starts with the onEdit(e) simple trigger. This is a special trigger in Google Apps Script called a Simple Trigger that will run every time you edit your Google Sheet. If Apps Script sees the onEdit() function in your project it runs automatically.

Every time onEdit() is executed, it will retrieve an event parameter that is commonly referenced as "e" (onEdit(e)). The event object can return things like:

  • The current value
  • The previous value
  • The edited range
  • The source spreadsheet
  • The trigger id
  • The authorisation mode

We will be using a few of these in our code below.

Unless you have a very short coding task, it is generally good practice to reference another function that will be executed when onEdit() is run rather than coding directing within the onEdit() function.

Take a look at the three examples I added for my test project for this tutorial:

You can see that each one of these functions will be run one after the other when a cell is edited and calls onEdit().

Count Every Time The Cell Changes

In this example, we add a count to our adjacent counter cell each time the cell is edited.

Note that we take the event (e) as our parameter for our countEveryTimeCellIsChanged(e) function.

Set the Variables

First, let’s assign all our variables at the top of our function to make it easier for us to change should we need to down the track.

We have 3 variables that we need to include here. They won’t change so we will make them a constant variable:

  1. tgtSheet – This is the target sheet tab name. For our example, this is assigned to “Single List”.
  2. tgtCol – This is the target column or the column that has the values that will need to be monitored for changes. In our example, this is the status column, column 3.
  3. counterCol – This is the column where we display our change count. For us, this is column 4, but it could be any selected column in the sheet.
Numbers correspond to the points above.

You can stop here if you want to copy and paste the script into your own project and update the variables for your own purposes. Or continue to learn how it all works.

Create and Publish a Google Workspace Add-on with Apps Script Course

Got a more specific problem you need help with, but don’t have the time to develop the skills? Make an enquiry on my ‘Hire me!’ page. I occasionally pick up projects. If I'm unable to right now, I can still help you find a great trusted freelancer.


Get the Correct Sheet Tab and Column

Lines 33-38

We don’t want to end up counting the wrong column or updating a completely different sheet tab by mistake. This means we need a way to check if we are in the right spot.

The first thing we need to do is check the range we are currently editing. This can be done by grabbing the range object in our event parameter. Line 33

We now have access to the Google Apps Script range class. From this class, we can get the sheet class and also the range column numberLine 34-35

Using the JavaScript ‘if’ statement we first check that the sheets name (sheet.getName()) is equal to our assigned target sheet name and if it is, then check that the selected column is equal to the target column. Line 38

Update the Counter Cell

Lines 40 – 48

We need to update the adjacent counter cell in the same row that was changed when edited.

We can use the range class again with the getRow() method. This will give us a row number. Line 40

const row = range.getRow();

Next, using the sheet variable we created earlier we can call the getRange() method to get the counter cell. For a single cell, this method can take two variables the row and the column number. Here, we add in these two variables we collected earlier.

const counterCellRange = sheet.getRange(row, counterCol);

Now we need to add one to the current count in our counter cell. This means that we first need to collect the current value in the cell, which we do with the getValue() method.

We need a way to check if the value in the counter cell is empty. If it is then we want to give it a value of 1 otherwise we want to add 1 to the current value.

const counterCellVal = counterCellRange.getValue();

This can be achieved with a ternary operation. Think of a JavaScript ternary operator as a single line if statement in a way.

The ternary operator below states that if the value is less than zero (empty) then add one otherwise add one to the counter cell value.

const updateCounterCell = (counterCellVal < 0)? 1 : counterCellVal + 1;

Finally, we update the counter cell:

counterCellRange.setValue(updateCounterCell);

Done!

 

The following scripts will modify this code. We will touch on the changes only in the code discussion of each script.

Count Every Time The Cell Changes To the Selected Value

In this scenario, we only want to count when a cell changes to a specific value. For our example, this value is  “NEEDS EDIT”.

First off, we need to store a target value that we want to monitor for and if that value appears add one to our counter cell. Here on line 29, we set our target value to “NEEDS EDIT”. 

const tgtVal = "NEEDS EDIT"

You can change this to the value that you need for your project.

Next, we need to check if the cell that was edited was edited to the new target value before we update the counter cell.

To do this we first get the value of the selected range (Line 42):

const cellVal = range.getValue();

Then we create an if statement checking that the cell value matches the target value on line 43 and closes on line 54.
if(cellVal === tgtVal){

Apply The Script to Multiple Selected Google Sheets

What if we have different editorial teams or genres handled by different editors? We might want to put the data for each team in a different sheet tab to make it easy for monitoring. Here we will need to check all the selected sheets for any changes.

In our example now we want the “Team 1” and “Team 2”  sheet tabs. Both tabs are identical in format.

Monitoring multiple sheet tabs for changes to a cell value onEdit in Google Sheets with Apps Script

Let’s see how our code works now:

Two changes need to be made to the script here.

On line 25, we need to change the tgtSheet variable to tgtSheets and include an array of all the Google Sheet tabs we need to monitor with our Apps Script code.

const tgtSheets = ["Team 1", "Team 2"]; // The sheet names the data is on.

Then on line 38, we need to replace the first part of the if statement with a way that will look through our array of sheets and check if the current sheet contains the selected name.

This can be achieved with the JavaScript includes function. This function appends a selected array (In our case, the target sheets array) and takes a target value as an argument. Our target value is the select sheet that this being edited.

... tgtSheets.includes(sheet.getName()) ...

Get the Aggregate Count of the Changes

This final script checks all the cells in the target column in a single Google Sheet tab and checks if they have been changed to “NEEDS EDIT”. Then records the aggregate count of all the cells in a single counter cell in a separate sheet tab.

In our example, we will monitor the “Single  List” sheet tab for our desired change and record the total count of changes for all rows in the “Aggregate” sheet tab.

Calculate the aggregate of all changes to cells to the target value in Google Sheets With Apps Script

Check out the code:

First, we have replaced the counterCol variable with the counterCell variable and set that to cell A2 of the ‘Aggregate’ sheet tab.

const counterCell = 'Aggregate!A2'; // The column to update the counter.

Next, we don’t need to get the row number this time so we have deleted this variable.

Lastly, on line 44, we use the source property of the event (e) object to grab the current spreadsheet. This allows us to use the getRange() method to select our counter cell.

Conclusion

There should be enough samples there for you to work out your own combinations. If you want to test your new skills out, try to:

  1. Create an aggregate count of multiple sheet tabs.
  2. Move the counter column to another location in the sheet.
  3. Check for different target values in different sheets with different locations.

If you get stuck check out some of the other popular tutorials on using onEdit():

It’s always nice to hear how people use these scripts in their own projects and it can be inspiring to share your use cases with others. Feel free to share your experience and examples in the comments below.

Finally a big thanks to Mike Kanert for the inspiration for this post. Editor, writer, artist, actor, educator, polymath and all-around nice fella, you can find Mike on Instagram @Unremarkable_Us for his latest comic series made by parents for parents:

Unremarkable Us

If you have found the tutorial helpful, why not shout me a coffee ☕? I'd really appreciate it.

~Yagi

Sort and randomize data in Google Sheets with Apps Script

You might want to sort some Google Sheet data based on a condition in your Google Apps Script or simply sort by selected columns and rows after inserting a new row.

If you are always building sample data for tutorials like me, you might also be interested in randomising your data programmatically.

Fortunately, Google Apps Script’s SpreadsheetApp Class has built-in methods to handle just that for you. Plus it’s super easy to implement.

Check out the video tutorial below or grab one of the code snippets to add into your own project.

Continue reading “Sort and randomize data in Google Sheets with Apps Script”

Add a Row of Data Below the Header in Google Sheets with Apps Script

Sometimes it is just more intuitive to add a row of data to a Google Sheet just below the header instead of at the bottom of the Sheet.

One instance when this comes to mind is when we need to monitor time-dependent data as it is being input programmatically based on a trigger through Google Apps Script. Seeing purchases or sign-ins at the top of your sheet live might be more convenient than at the bottom of the sheet.

In this tutorial, we will walk through how to insert a row and add a new row of data to that row. We’ll also look at how to maintain formatting during this process.

Then if you need to maintain a Named Range or Some formula columns, we’ll walk through that too.

Let’s dive in!

The Starter Sheet

If you want to try out the script examples directly or are playing along from the video tutorial, here is a copy of the starter script:

Add a Row of Data Below the Header with Apps Script – STARTER

The Video

The Basic Code to Add a Row after A Header

In this example, we have a simple Google Sheet where we need to add our data to a row just below the header.

Simple example of adding a row below the header in Google Sheets With Apps Script
A simple example of adding a row below the header in Google Sheets With Apps Script

The main run function is the insertRowAtTop_v1() function. This contains all the code we need to complete our task.

The runsies_example1() function is simply a sample function that simulates how you can integrate the insertRowAtTop_v1() function into your own project.

If you have found the tutorial helpful, why not shout me a coffee ☕? I'd really appreciate it.

insertRowAtTop_v1()

Set up the function

The insertRowAtTop_v1() function takes 3 arguments:

  1. data – This is a 2d array of data containing the values you want to enter into your new row.
    Google Sheet data is presented as a 2d array in Google Apps Script where a row of data is an entire array and each row array is contained in an outer array. So for example, a sheet range that is 3 columns by 4 rows deep would look like this:
  2. sheetName – This is the sheet tab name.
  3. targetRow – This is the row where you want to insert your range. If your header data is deeper than one row, then you can change this value.

First, we call the getActiveSpreadsheet() method from the Google Apps Script SpreadsheetApp class. You can use the openById() or openByUrl() methods.

Now that we have access to our sheet we can invoke the getSheet() method taking the selected sheet as our parameter. For our example, the sheet will be ‘Example 1’ as indicated by the sheetName parameter.

insert a row into the sheet

Our next task is to add a row to our sheet. We can do this with the .insertRowBefore(targetRow) method. This method takes a target row as a parameter. For us, our argument for this parameter is row 2Line 21

This method will insert a row above the target row.

Now you might have also seen the .insertRowAfter() method and may be wondering why this was not used. These insert methods reference their formatting (i.e. Font, font colour, borders, background colours etc.) from the target row. So if we used the ‘after’ method we would have the header formatting in our cell, which is not what we want.

add data to the new row

With the new row added, we can add the data to that range.

First, we need to get the range where we will add our data. We do this with the getRange() method. This can take a number of parameter formations, but we will use the 4-integer set. These include:

  1. Row Start – This will be the target row we set.
  2. Column Start – All our data will begin in Column A so we set this to one.
  3. Row Depth – We are only inserting one row of data so our depth will be one.
  4. Column Width – The width of the range will be determined by the width of the data that we will put into it. This means that if we get the length or total count of each cell item in the row then this will be the column width.
    We can do this by using the JavaScript length property. Note that we need to get the length of the inner array (data[0].length). This is done by referencing the first or zeroeth item in the array. Line 23

Now that we have our range we can set the values from our data parameter. We do this using the setValues() methodLine  24

Flush the Spreadsheet

Flush Your SpreadsheetWhile Google Apps Script will try and bundle your data together into one operation, a Flush can ensure that all operations on the spreadsheet are complete before any other operations are done on the sheet.

We achieve this with the SpreadsheetApp.flush() method.

runsies_example1()

This example function helps us to understand what parameters we can enter into insertRowAtTop_v1().

Here we set our target row to row 2 and the sheet name to ‘Example 1’.

For our dummy data, we will abuse the JavaScript Date constructor to generate the current date time stamp (Line 39). Then we will convert the date to a time in milliseconds with the getTime() method (Line 40).

We will then use the time as an ID, the date as, well… the date and build a dummy email from the time and a ‘@example.com’ string.

After that, add these variables into the function insertRowAtTop_v1() call parameters.

Handling The Edge of a Named Range

In this example,  we have added a named range to our data range. Perhaps to make it easier to handle the data in another operation in our sheet or in our script.

However, if we were to run our script in ‘Example 1’ above on this we would find that after inserting a new row, our Named Range will change its range. It will go from ‘Example 2’!A2:C15 to ‘Example 2’!A3:C16  as you can see in our Example 2 sheet DataSet named range in the image below. Each subsequent addition to the range will add another row to the DataSet named range.

This will cause problems for us because it will miss the new data we add.

We need to fix this.

Adding a row below the header in Google Sheets with a Named Range With Apps Script

Check out the code for our updated function insertRowAtTop_v2():

Update insertRowAtTop_v2()

In this updated version we have added an extra parameter called namedRangeName. In our example, this will be the DataSet named range. Line 8

On lines 22-26, we need to update the named range, maintaining the existing row and column height, and column width but expanding its row depth.

Two new variables

Lines 13 and 14 add two new variables to our function and will be used to recalculate the range of our named range:

  1. rowDepth: This is the last row number subtracted by the target row + 3. In the example image above our target row is row 2. The last row with data is row 14. If we subtract 14 from 2 we get 12. However, we have 13 items in our sheet and our name range also included an empty space at the bottom – Which means we need to add 2. We will also add an extra row during our process so now our formula would look like this:
    14 - 2 + 3 = sheet.getLastRow() - targetRow + 3
  2. colWidth: We will now need to get the column width twice from our data parameter. Once when we get the range and, once when we get the width of the named range. Let’s call the length property only once (data[0].length) and then reference the resulting value.

find the named range and update it

The only way we can update a named range with the SpreadsheetApp class is to first get an array of all named ranges in the spreadsheet. This is done with the getNamedRanges() methodLine 22

From here we can use the JavaScript Find method that will return the first matching item in the array. Line 23

.find(namedRange => namedRange.getName() === namedRangeName)

The Find method takes a function as an argument containing an iterator parameter that we set to namedRange. This will run through each named range in the array until it finds our desired match.

For simplicity, we are using an arrow function here.

On each iteration, we can call the getName() method on the selected named range and compare that with our namedRangeName parameter (e.g. ‘DataSet’). If there is a match Find will conclude its iteration and return the current named range.

Once we have our selected named range we can use the setRange() method to update the range (Line 25). This method takes another range construct as an argument (Line 26).


Here we set the start row to our target row in column A and then assign our row depth and column widths we generated earlier in the function.

Including preexisting formulas into the newly inserted row

You may have a scenario where you have some formulas in your sheet that you need to add to your data row when it is added to your Google Sheet.

In our ‘Example 3’ sheet tab, we have added two formula rows to our data set in columns D and E that we want to include in our new row entries.

Adding a row below the header in Google Sheets with a Named Range and formula columns With Apps Script

Let’s take a look at the updated function now:

Here we have included a new optional parameter hasFormulas that is set to false by default. You can set this option to true if you wish to include formulas at the end of your row input. Line 9 

Adding the formulas to the input

We have included an if condition after we created our row on lines 21-27. Here we check if the sheet has formulas at the end. If it does then get the total number of columns in the sheet with the getMaxColumns method.  Line 22

Next, we need to get the ranges for the newly inserted row (rowAbove) and the row below it (rowBelow). We will add one to the row to grab the row below and apply the maxCols variable from line 22 to indicate the width of the range.

Finally, we will copy the row below in its entirety to the row above with the copyTo method.

The format for this method is as follows:

sourceRange.copyTo(destinationRange)

Without any other optional parameters, this method will copy over the formatting and the formulas from the previous range. If the formulas are not set to absolute ranges (with the $ sign) then they will adjust automatically to the new cell range.

Conclusion

While there are probably still some edge cases that these examples don’t cover, I think that they will handle the majority of scenarios that you come across. The examples should also provide you with some understanding of the workings of the code to make your own modifications where you see fit.

It’s always interesting to hear how you apply the scripts to your own projects. Feel free to comment below.

Further Reading

If you are looking for an alternative approach to adding data to a sheet check out these tutorials:

Create and Publish a Google Workspace Add-on with Apps Script Course

Got a more specific problem you need help with, but don’t have the time to develop the skills? Make an enquiry on my ‘Hire me!’ page. I occasionally pick up projects. If I'm unable to right now, I can still help you find a great trusted freelancer.


If you have found the tutorial helpful, why not shout me a coffee ☕? I'd really appreciate it.

Develop a Google Chat App Currency Converter with Google Apps Script

Have you ever wanted to convert currencies instantly while in Google Chat with colleagues and clients? In this tutorial, we are going to build a Currency Converter Google Chat App with Google Apps Script to do just that.

This tutorial is a multi-media series containing step-by-step video instructions for each stage of the process along with code snippets for each stage, important links and some further details on the more unusual parts of the code.

We start off our project adventure having already made a copy of the Google Apps Script Chat App project, connecting it to a Google Cloud Platform (GCP) project and deploying it for the first time. You can find out how to set up this stage by heading over to the Google Chat Apps for Google Apps Script Developers tutorial or directly to the YouTube tutorial.

It is important to note that Chat Apps and their development are only available for Google Workspace paid accounts.

What we are Building

In this tutorial, we will be creating a Currency Converter chat app that generates a currency conversion based on a slash command inside Google Chat or Space. The syntax will be:

/xe  amount from_currency_code:to_currency_code

For example:

/xe 1,230.95AUD:USD

This will return

 1,230.95 AUD =  795.2 USD

1 AUD = 0.64601 USD
1 USD = 1.54798 AUD

(Last updated: 2022-10-07)

Convert a currency inside a Google Chat Space Example

We will also provide two more slash commands:

  1. /xe-help – This will provide instructions for the user on how to enter a conversion.
    Google Chat App Currency Converter xe-help slash command example
  2. /xe-list – This will provide a list of all currency codes.
    Google Chat App Currency Converter xe-list slash command example

Create and Publish a Google Workspace Add-on with Apps Script Course

Got a more specific problem you need help with, but don’t have the time to develop the skills? Make an enquiry on my ‘Hire me!’ page. I occasionally pick up projects. If I'm unable to right now, I can still help you find a great trusted freelancer.


1. Create the /xe and /xe-help slash commands

In this first part of the tutorial series, we need to create two main slash commands /xe and /xe-help. To do this we will update the onMessage() trigger function. Then we will connect the slash commands in the GCP Google Chat API configuration GUI.

We will also update the Google Apps Script Chat App template to return messages more specific to our currency converter.

Video 1

If you have found the tutorial helpful, why not shout me a coffee ☕? I'd really appreciate it.

The Code

Code.gs

CurrencyGlobals.gs

SlashCommands.gs

appsscript.json

In your appsscript.json file, add the following:

For me this would look like this:

2. Connecting our Google Apps Script to the Exchange Rates Data API

At this stage of the tutorial, we will build our connector to our currency exchange API. We will create a quasi-class (CurrencyAPI()) with a method to get a list of all currencies (.getCurrencyList()) and retrieve a currency conversion (.convertCurrency()).

Accessing the API is done through the Google Apps Script UrlFetchApp Class with the .fetch() method. This will return two important methods worth noting:

  1. .getContentText() – The returned text from the fetch. For us, this will be a stringified JSON.
  2. .getResponseCode() – The response code. 200 to indicate a good response and the rest of the codes are errors. You can see the full list of error codes from the API here.

After that, we will need a way to check for any error codes that our fetch request may generate. We will create a private function for this to return either the text if the request is successful or error information.

We will be connecting to the Exchange Rates Data API. The API has a free tier of 250 requests each month. There is no requirement for a credit card or anything.

Video 2

Tutorial Links – For part 2

The Code

CurrencyAPI.gs

Create this file to store your Currency API connector.

Test.gs

Create this file to use for testing certain stages of your project.

CurrencyGlobals.gs – Add to. 

Add the currency code to the bottom of this file.

3. Connecting our Google Apps Script to the Exchange Rates Data API

Before sending our slash command info from our Google Chat App to the API to retrieve the currency conversion, we need to ensure that the user has provided valid input.

The expected input from the user is as follows:

/xe [amount]source_currrency_code:destination_currency_code

For example,

/xe 115.44AUD:USD

Note! Before we dive into our validation, it’s important for me to point out that I am basing my validation rule on the UK and US English separator convention of 1,000,000.00 or 1000000.00. Please modify the rules to meet your own country’s requirements.

What we will allow

It’s okay, particularly when working with text inputs, to be a little flexible in how a user might input their currency conversion.

If you have ever gone on Google search and run a currency conversion, you know that you can make a number of combinations to generate a currency conversion.

While coding out the full extent of Google’s allowable types would be far too complex and perhaps, dare I say, boring, we can provide a little help. Here is what we can do to support user input variation:

  1. Allow for any number before the currency code section.  E.g. 1 or 1,110,00 or $2300.00.
  2. Allow for the use of commas or no commas in the amount that users input. E.g. 1,000,000,000.00 or even mistakes 1,1,1,222,1,.00.
  3. Provide some spacing flexibility between:
    1. The amount and the currency code section. E.g. 20     AUD:USD or 20AUD:USD.
    2. The source currency code and the colon ( Up to 3 spaces should be enough). E.g. 20AUD :USD or 20AUD   :USD or 20AUD:USD.
    3. The colon and the destination currency code (Again, up to 3 spaces should be adequate). E.g. 20AUD: USD or 20AUD:   USD or 20AUD:USD
  4. Permit lowercase currency code or mixed case code. It costs us nothing to convert everything using the toUpperCase() JavaScript method. E.g. 20aud:UsD.

What will generate an error

On the other hand, there are some necessary items for us to send to the API in order for it to respond successfully with currency conversion. This means we should handle:

  1. Missing number. No number no conversion. Error e.g. /xe or /xe AUD:EUR
  2. Multiple decimal places. It is hard for us to guess where the user intended to add their decimal place so we need to return an error here. Error e.g. 2,333.21.24 or E.g. 2..34.561.01.
  3. Ridonculous amount. 🤯 Extreme amounts may be difficult for the API to handle are likely someone is being a little silly. We should respond in kind. Error e.g. 1126545465165198998118991981891.1656116165165156165165161651165
  4. Non-3 letter currency codes. All currency codes are 3 letters in length. Error e.g. 2A:USD or 4AUD:US
  5. Missing source and destination currency code or colon. If we don’t have a source or a destination code we can’t convert anything. Error e.g. 2:EUR or 2AUD or 2AUD:.
  6. Erroneous currency codes. We should check with our stored CurrencyCodes list before we waste valuable requests with the Currency Exchange API. Error e.g. 2XXX:USD or 2AUD:YYY.

Setting up the code

We will create the validateAndRetrieveCurrenciesAndAmount(text) function to handle our validation. This will be called from the attemptConversion() function after it receives the text from the /xe slash command.

Inside our validation function, we will extract our amount and currencies separately. This is because they require us to look at different things to ensure that they are accurate and ready to be sent to the API. This also helps us vary the spacing between the amount and the currency codes should they add a space.

It is much less costly and more efficient for us to run validation Apps Script-side rather than lose a request credit and let the API handle the error.

If we discover an error in the user’s input, we will return a text string to then containing information about the nature of the error. We will also include our instruction information contained in the errorInstructions variable.

If the user successfully enters their currency code, then our validation function will return an object containing the amount as a float, the source currency code and the destination currency code.

conversion = {source, destination, amount}

Regular Expressions

We will be using a variety of regular expression rules to achieve the majority of our validation here.

The Creator Of Regular Expressions
Regular Expressions were invented by Stephen Kleene, a known Priest of  Silencio, who designed the syntax to be rendered instantly forgettable if attempted to be learnt.

Because they can be a little tricky we will explain them here in a little more detail:

  • Extract the amount: /([\d.,]+)/:
    • [] – Indicates a character class or range to look for.
    • \d – Search for any digit.
    • ., – Search for any decimal (.) or comma (,)
    • () – Ensures that all elements are captured in a group where we can apply a quantifier to it like we have with the plus symbol.
    • + – matches one or more occurrences of the selected characters.
  • All periods or decimal symbols: /\./g:
    • \. – Search for a period.
    • /g – The global flag matching all occurrences of the selected search.
  • From and to currency code range: /[A-Za-z]{3}[\s]{0,3}:[\s]{0,3}[A-Za-z]{3}/
    • [A-Za-z] – Character class searching for any character within the alphabet with either upper or lower case.
    • {3} – Curly braces indicate a match of a specific number of times. If the braces have one argument it must strictly meet that number of occurrences.
    • [\s] – Character class search for spaces.
    • {0,3} – Matching a range of the preceding character or character class between two values.
    • : – Match a colon.
  • Get each currency code: /[A-Za-z]{3}/g:
    • [A-Za-z]{3}– The 3-letter code containing any letter from A to Z in any case.
    • /g -Any occurrence of the selected search item.

Video 3

The Code – SlashCommands.gs

attemptConversion()

Remove the placemarker: return xe text = "${text}".

Add:

 

validateAndRetrieveCurrenciesAndAmount(text)

Add the following function.

 

4. Connecting /xe slash command to the API and validation

Now we finally get to deploy our /xe slash command and get some results.

First, we need to update the returned item in our attemptConversion() function with the currencyConversion() function (see code below).

The currency conversion function will call the Exchange Rate API via our CurrencyAPI().convertCurrency() method. If successful, it will return the currency based on the inputs we have validated and send it as part of the payload to the API.

We could simply return a value (e.g. xe/ 10AUD:USD = 6.19865 ) but that does not provide a lot of context for our users instead we want to provide something with a bit more valuable that will include:

  • The returned result: 10 AUD = 6.19865 USD
  • 1 source value = destination value: 1 AUD = 0.619865 USD
  • 1 destination value = source value: 1 USD = 1.613254 AUD
  • The date the exchange rate was found: (Last updated: 2022-10-16)

Convert a currency inside a Google Chat Space Example

We can retrieve all but one bit of these from the object that is returned from our request:

The only thing we need to work out is the conversion of 1 destination value base to the source. We can do this by dividing 1 by the exchange rate.

1/result.info.rate

However, there is a spanner in our works…

spanner in the works

JavaScript Decimal Rounding Errors

The Problem

Our exchange rate returns a value up to 6 decimal places (e.g. 1.123456). This is more than enough to get a fine-grained indication of the exchange rate. Besides, it would look pretty message with a huge string of trailing decimal digits.

A problem arises in Javascript when we try and round up a value using the JavaScript toFixed() method.

Let’s say we have the number, 5.5555555, and we want to round up from 7 decimal places to 6. Our primary school education taught us that this should be 5.555556. However, using the toFixed() method we get. 5.555555. If we were to increase the number in the seventh decimal position to 6, 7, 8, or 9 all would be right in the world and it will round up as expected.

The Solution

How do we resolve this?

I found a really good solution shared by George Birbilis in StackOverflow. It does, however, warrant some explanation. Here is my version of the code:

+(Math.round(Number(weirdNum + "e+6")) + "e-6")

The ‘e’ here represents the exponent value. You will often see this when you are logging a huge number in JavaScript. It’s a kind of short-hand version.

For example, 5e6 would be:

= 5 * 10^6  (or to be more granular 5 * 10^+6)
= 5 * 10 * 10 * 10 * 10 * 10
= 5 * 1,000,000
= 5,000,000

So when we convert our weirdNumber variable plus “e+6”  with the Number constructor we are moving it left 6 decimal places.

= Number(weirdNum + “e+6”)
= Number(5.5555555 + “e+6”)
= Number(“5.5555555e+6”)
= 5555555.5

Now we can use the JavaScript Math.round() method to round the last decimal place to the correct value.

= Math.round(5555555.5)

= 5,555,556

Next, we need to convert the number back to the correct decimal value by reversing the exponent value we set:

= +(5,555,556 + “e-6”)

Note the plus symbol at the start of the braces, this is a sneaky way of conversing an exponent number text string to a number.

Video 4

Video released 19 Oct 2022.

The Code

SlashCommands.gs

In the attemptConversion(text) function replace:
return xe text = "${(typeof conversion === "string")? conversion: JSON.stringify(conversion)}"
with
return currencyConversion(conversion);
currencyConversion()

 

If you have found the tutorial helpful, why not shout me a coffee ☕? I'd really appreciate it.

5. A Google Chat App Card for the list of currency codes

There are hundreds of currency codes that the users may wish to draw from and we can’t expect them to memorise them all. The easiest approach we have to support our users here is to provide them with a /xe-list slash command in their Google Chat or Space.

When the user returns the slash command, they will get a stylised card response:

Google Chat App Currency Converter xe-list slash command example

This looks a lot nicer than listing all the currency codes in a message.

This time around instead of returning a text object property we will be returning a card version 2 property.

Cards version 2 JSON

We will need to create a JSON to send to the Chat API to construct our card.

The card contains a header property and a section property.

In our project, we style our header with a title, subtitle and image. Also, note that cards can contain stylable headers as well if you choose to use them.

The section sub-object contains an array of all of the sections that you want to add. Sections provide visual separation in the card and are useful for us to separate our currency codes by letters of the alphabet for ease of reading.

Inside each section, you can add a number of widgets set as an array. There are heaps of widgets to choose from that we noted in our Google Chat App for Developers tutorial.

From the widgets list, we used the Decorated Text widget. It has a wide range of uses from button clicks and better styling to adding icons and even switch controls.

We only needed to use the top label property to add our letter and then generate our list of currency codes and their descriptions for that letter using the text property.

Image link

If you want to use the same image in your project you can find it here:

https://images.unsplash.com/photo-1599930113854-d6d7fd521f10?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=880&q=80

The image is by globe maker, Gaël Gaborel.

Video 5

Video released 22 Oct 2022.

The Code

Code.gs – onMessage()

Add a third switch option for /xe-list

Don’t forget that you will need to go to your GCP console for your project. Select APIs and Services. Then scroll down to find your Chat API.

In the Chat API, select configuration. Scroll down to the Slash commands select and add a new slash command.

The details will be:

  • Name: /xe-list
  • Command ID: 3
  • Description: List of all currency conversion codes.

Select ‘Done’ and then save the configuration.

CurrencyCardList.gs

Create a new file called CurrencyCardList.gs. Here you will add the following function.

createListCardResponse()

 

Conclusion

That wraps up our Currency Converter Google Chat App built-in Google Apps Script.

There are a bunch of further directions we could go with the chatbot. We could add an API key input dialogue for each user to add their own API key for the Exchange Rage API.

Alternatively, we could create a customisation dialogue that will allow the user to create a custom display format and input type for their specific region. After all, not all currencies are written the same in different countries.

Another thing we could do is to create a dialogue when users just add /xe instead of appending an amount, source and destination code. Then we could rely on selection boxes for users to choose their currencies, and even add a date.

What else can you think of to improve this project? I would love to hear in the description below.

This was an enormous project to put together taking up several months of preparation, content writing and video creation. I hope you got something out of it.

If you have found the tutorial helpful, why not shout me a coffee ☕? I'd really appreciate it.

~Yagi

Google Chat Apps for Google Apps Script Developers

In this article, we will look at Google Chat Apps and go over some of the basics of developing a chat app with Google Apps Script.

Note that Chat Apps are currently only available to Google Workspace paid accounts and not standard ‘@gmail’ consumer accounts.

What are Google Chat Apps?

All about Google Chat Apps YouTube Video
Click to go to the video!

Google Chat is a business-level messaging tool for Google Workspace that allows you to chat one-on-one and in groups called Spaces within your business and even with clients and contractors outside of your domain.

Its integrations with Google Workspace tools like Google sheets, docs and slides right inside chat along with a Google Task tab and a convenient file library tab make it a really handy business tool.

But one thing that is often overlooked is that you can add Chat Apps or Chat Bots to your Google Chat Spaces or DM a chat app directly to accomplish tasks conveniently.

There are hundreds of 3rd party Chat App integrations for you to choose from. Anything from project management to customer support and more.

Google Chat Apps Available

Chat apps can:

Continue reading “Google Chat Apps for Google Apps Script Developers”

%d bloggers like this: