UtterAccess.com
X   Site Message
(Message will auto close in 2 seconds)

Welcome to UtterAccess! Please ( Login   or   Register )

Custom Search
3 Pages V  1 2 3 >  (Go to first unread post)
   Reply to this topicStart new topic
> Google Calendar Solved, Access 2016    
 
   
gtsolano
post Jan 12 2017, 08:36 PM
Post#1



Posts: 359
Joined: 21-January 06



Microsoft Access and Google Calendar

One of the first things you have to do is setup your primary Google Calendar. Log into Google and create your primary calendar. Next you must get your client_id and client_secret from google. Go to https://console.developers.google.com/ to obtain your google credentials. You must go to the Library and select the Calendar API. Under the Credentials section, you must assign a project name (what ever you want to call it) and create credentials. There you will find you client_id and client_secret. You will need these later.

Your SCOPE for Google calendar is https://www.googleapis.com/auth/calendar

High Level Overview of the Process

One of the first things that needs to happen when communicating with Google calendar is connecting and get an access token. You need this access token to add/edit/retrive informaiton from your calendar.

- Obtain your client_id and client_token (as discussed above)

- Figure out a method of storing/retreving in your application (I created a public variable that stores them so I can use when I need them. I created a table that stores/reteives them when my application starts.

- Use OAuth2 to obtain an access token and refresh token. Save these tokens in your storage choice for later use. WIth tokens, your access token is short lived (about 6 hours) and then it expires. Once it expires you use the refresh token to re-up your access token. One you get a access token with the refresh token, you do not have to re-initialize or get new tokens as long as the access token is valid (not revolked).

When you first get an access token, a browser form will appear and will ask you to log into your Google account. It needs your username and password and you must allow the app to access google. Once this is done, you will capture the access and refresh tokens.

On subsequent calls to Google, the app uses stored access and refresh tokens and you will not need to authorize again.

- Send requests to Google calendar and get results. A good reference for syntax and usage is https://developers.google.com/google-apps/c...r/v3/reference/ . They have a sand box where you can select your parameters and try on your calendar to insure they work. Then you can copy the JSON text and use in your calls.

- All output from Google calendar is send via JSON text. Included is a JSON Parser that will create a two dimentional array that stores the key and value of the received output. From here you can process depending you your needs. I also included sample code for listing calendar info, add secondary calendar, add calendar event, delete calendar event, and update calendar event.

When you create a calendar event a unique ID number is created. You use this ID number to reference that event. When you update (change date, time, etc) the ID number stays the same so you can store the ID number in you app and use it to call detials about the scheduled event. That way when someone is using Google calendar and changes something you reference will not change but your data for the event will. The only problem exixts when they delete an event, you ID dies with it so you must create events in you app so they will sync correctly.

Lets get started....

Create a new form (we will call it BROWSER) and add a WebBrowser control named WebBrowser1. We will use this when we need to authorize in Google so we can get access tokens.
Your Form_Load event should include code to populate your client_id and client_secret items (I used LoadCalendarInfo function...listed later)

BROWSER Form Code

Option Compare Database
Option Explicit
Private Enum BrowserNavigationFlags
navOpenInNewWindow = 1 ' Open the resource or file in a new window.
navNoHistory = 2 ' Do not add the resource or file to the history list. The new page replaces the current page in the list.
navnoreadfromcache = 4 ' Do not read from the disk cache for this navigation.
navNoWriteToCache = 8 ' Do not write the results of this navigation to the disk cache.
End Enum
Private Sub Form_Load()
'assign google api settings to variables '<<create registry keys to save and pull
Me.WebBrowser1.Object.Navigate "about:blank", navnoreadfromcache
startOauth2
End Sub
Private Sub WebBrowser1_NavigateError(ByVal pDisp As Object, URL As Variant, TargetFrameName As Variant, StatusCode As Variant, Cancel As Boolean)
' Due to the redirect URL pointing to Localhost and we don't have a webserver running at localhost (Just make sure we don't!)
' The browser triggers the NavigateError event when it receives the URL for localhost
' We can now read the URL and extract the received code to request a token
Dim retCode, getAccessToken As String
Dim authres() As String
Dim aCnt As Long
' Extract the code from the URL
retCode = right(URL, Len(URL) - (InStr(1, URL, "&code=") + 5))
' Construct the Body to request a access token and a refresh token
getAccessToken = "code=" & retCode & "&" & _
"client_id=" & OAuth2.oauth2_client_id & "&" & _
"client_secret=" & OAuth2.oauth2_client_secret & "&" & _
"redirect_uri=http%3A%2F%2Flocalhost&" & _
"grant_type=authorization_code" '?&
' Send the request
SendHTTP "https://www.googleapis.com/oauth2/v4/token", "POST", "application/x-www-form-urlencoded", getAccessToken
' And receive the tokens
authres = Split(HTTP_ResponseText, """")
Debug.Print HTTP_ResponseText
' Now extract the tokens from the received body
aCnt = 0
While aCnt < UBound(authres)
aCnt = aCnt + 1
If authres(aCnt) = "access_token" Then OAuth2.oauth2_access_token = authres(aCnt + 2)
If authres(aCnt) = "token_type" Then OAuth2.oauth2_token_type = authres(aCnt + 2)
If authres(aCnt) = "refresh_token" Then OAuth2.oauth2_refresh_token = authres(aCnt + 2)
Wend
SaveCalendarInfo 'Function that saves newly received tokens
' And we are done
DoCmd.Close acForm, "Browswer"
End Sub

Private Sub startOauth2()
' Here we start stage 1 of the oAuth2 process
Dim svarbody As String
' First we create a body to request access
svarbody = "client_id=" & OAuth2.oauth2_client_id & "&" & _
"redirect_uri=http%3A%2F%2Flocalhost&" & _
"scope=" & OAuth2.oauth2_scope & "&" & _
"response_type=code&" & _
"access_type=offline"
' Send the request
SendHTTP "https://accounts.google.com/o/oauth2/v2/auth", "POST", "application/x-www-form-urlencoded", svarbody
' And write the result to the WebBrowser control on the form
If HTTP_Status = 200 Then
Do While Me.WebBrowser1.Object.ReadyState <> 4: DoEvents: Loop
Me.WebBrowser1.Object.Document.Write (HTTP_ResponseText)
Else
MsgBox "Unable to connect", vbInformation, "Warning..."
DoCmd.Close acForm, "GetOAuth2"
End If
End Sub

OAuth2 Authorization

Add references:
(Click Alt-F11 and click on Tools->References and add the following)
Microsoft Scripting Runtime
Microsoft XML, V6.0


OAuth Module:
Add a new module and name it OAUTH and copy the code below. This module is used to send and receive requests to Google.


Option Compare Database
Option Explicit
'Create public variables for storing response from http request
Public HTTP_Status As Long
Public HTTP_StatusText As String
Public HTTP_ResponseText As String
'Google OAuth2 settings
Type Auth
oauth2_client_id As String 'Provided by Google
oauth2_client_secret As String 'Provided by Google
oauth2_scope As String 'Provided by Google
oauth2_access_token As String 'Obtained after OAuth Request
oauth2_refresh_token As String 'Obtained after OAuth Request
oauth2_token_type As String 'Obtained after OAuth Request
End Type
Public OAuth2 As Auth
' This sub sends the request and collects the results
' Content should be "application/json" for Google requests
Public Sub SendHTTP(URL As String, Optional Method As String = "POST", Optional content As String = "text/plain", Optional Body As String = "", Optional addAuth As Boolean = False, Optional Headers As Variant)
Dim Http As MSXML2.XMLHTTP60
Dim hdrLine As Variant, hdrarr As Variant, blDebug As Boolean
'Set to true for debug
blDebug = True
Set Http = New MSXML2.XMLHTTP60 ' may be different depending on what version your Microsoft XML reference is. Earlier version remove 60 at end
With Http
Call .Open(Method, URL)
If content <> "" Then Call .setRequestHeader("Content-Type", content)
If addAuth Then Call .setRequestHeader("Authorization", OAuth2.oauth2_token_type & " " & OAuth2.oauth2_access_token)
If IsArray(Headers) Then
For Each hdrLine In Headers
hdrarr = Split(CStr(hdrLine), ":")
Call .setRequestHeader(hdrarr(0), hdrarr(1))
Next
End If
Call .send(Body)
HTTP_Status = .status
HTTP_StatusText = .StatusText
HTTP_ResponseText = .responseText
If blDebug = True Then
Debug.Print "--------------------------------"
Debug.Print HTTP_Status
Debug.Print HTTP_StatusText
Debug.Print HTTP_ResponseText
Debug.Print "--------------------------------"
End If
End With
Set Http = Nothing
End Sub
' A function that checks if the known token is still valid and tries to request a refresh token if it is not
Public Function CheckToken() As Boolean
SendHTTP "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=" & OAuth2.oauth2_access_token
If HTTP_Status = 200 Then
CheckToken = True
Else
CheckToken = RefreshToken
End If
End Function
' A function that requests a refresh token
Public Function RefreshToken() As Boolean
Dim authres() As String
Dim svarbody As String
Dim aCnt As Integer
svarbody = "client_secret=" & OAuth2.oauth2_client_secret & "&" & _
"grant_type=refresh_token&" & _
"refresh_token=" & OAuth2.oauth2_refresh_token & "&" & _
"client_id=" & OAuth2.oauth2_client_id '?&
SendHTTP "https://www.googleapis.com/oauth2/v4/token", , "application/x-www-form-urlencoded", svarbody, False
If HTTP_Status = 200 Then
authres = Split(HTTP_ResponseText, """")
aCnt = 0
While aCnt < UBound(authres)
aCnt = aCnt + 1
If authres(aCnt) = "access_token" Then OAuth2.oauth2_access_token = authres(aCnt + 2)
If authres(aCnt) = "token_type" Then OAuth2.oauth2_token_type = authres(aCnt + 2)
If authres(aCnt) = "refresh_token" Then OAuth2.oauth2_refresh_token = authres(aCnt + 2)
Wend
SaveCalendarInfo 'Call function that saves results (access_token, token_type, refresh_token)
RefreshToken = True
Else
RefreshToken = False
End If
End Function
' A sub to revoke a known token
Public Sub RevokeToken()
If CheckToken() Then
SendHTTP "https://accounts.google.com/o/oauth2/revoke?token=" & OAuth2.oauth2_access_token
If HTTP_Status = 200 Then
RemoveCalendarInfo 'Call function that deletes calendar info (access_token, token_type, refresh_token)
End If
End If
End Sub


Other Support Module

Add References:
Microsoft ActiveX Data Objects 2.5 Library

Create a new module called GOOGLESTUFF (or what ever you want) and paste in the following code:


Option Compare Database
Option Explicit
'Routine that opens a table dbo_CalendarInfo that populates global variables
'Should be run prior to executing http request
'You may choose to store / retreive differently based on you application needs
Public Sub LoadCalendarInfo()
Dim rst As ADODB.Recordset
Dim cnn As ADODB.Connection
Set cnn = CurrentProject.Connection
Set rst = New ADODB.Recordset
rst.Open "select * from dbo_CalendarInfo ", cnn, adOpenKeyset, adLockOptimistic
If rst.RecordCount > 0 Then
With OAuth2
.oauth2_access_token = Nz(rst!access_token)
.oauth2_client_id = Nz(rst!client_id)
.oauth2_client_secret = Nz(rst!client_secret)
.oauth2_refresh_token = Nz(rst!refresh_token)
.oauth2_scope = Nz(rst!scope)
.oauth2_token_type = Nz(rst!token_type)
End With
End If
rst.Close
cnn.Close
Set rst = Nothing
Set cnn = Nothing
End Sub

'Routine that opens a table dbo_CalendarInfo that saves global variables to table
Public Sub SaveCalendarInfo(Franchise As Long)
Dim rst As ADODB.Recordset
Dim cnn As ADODB.Connection
Set cnn = CurrentProject.Connection
Set rst = New ADODB.Recordset
rst.Open "select * from dbo_CalendarInfo" , cnn, adOpenKeyset, adLockOptimistic
With OAuth2
If rst.RecordCount = 0 Then
rst.AddNew
rst!client_id = Nz(.oauth2_client_id)
rst!client_secret = Nz(.oauth2_client_secret)
rst!scope = Nz(.oauth2_scope)
End If
rst!access_token = Nz(.oauth2_access_token)
rst!refresh_token = Nz(.oauth2_refresh_token)
rst!token_type = Nz(.oauth2_token_type)
rst.Update
End With
rst.Close
cnn.Close
Set rst = Nothing
Set cnn = Nothing
End Sub

'Routine that opens a table dbo_CalendarInfo that removes values for access_token, refresh_token, and token_type
Public Sub RemoveCalendarInfo(Franchise As Long)
Dim rst As ADODB.Recordset
Dim cnn As ADODB.Connection
Set cnn = CurrentProject.Connection
Set rst = New ADODB.Recordset
rst.Open "select * from dbo_CalendarInfo", cnn, adOpenKeyset, adLockOptimistic
With OAuth2
If rst.RecordCount > 0 Then
rst!access_token = Null
rst!refresh_token = Null
rst!token_type = Null
rst.Update
End If
End With
rst.Close
cnn.Close
Set rst = Nothing
Set cnn = Nothing
End Sub

'Function that splits data into a two dimentional array
Public Function SplitTo2DArray(ByRef the_sValue As String, ByRef the_sRowSep As String, ByRef the_sColSep As String) As String()
Dim vasValue As Variant
Dim nUBoundValue As Long
Dim avasCells() As Variant
Dim nRowIndex As Long
Dim nMaxUBoundCells As Long
Dim nUBoundCells As Long
Dim asCells() As String
Dim nColumnIndex As Long
If Len(the_sValue) = 0 Then
Exit Function
End If
' Split up the table value by rows, get the number of rows, and dim a new array of Variants.
vasValue = Split(the_sValue, the_sRowSep)
nUBoundValue = UBound(vasValue)
ReDim avasCells(0 To nUBoundValue)
' Iterate through each row, and split it into columns. Find the maximum number of columns.
nMaxUBoundCells = 0
For nRowIndex = 0 To nUBoundValue
avasCells(nRowIndex) = Split(vasValue(nRowIndex), the_sColSep)
nUBoundCells = UBound(avasCells(nRowIndex))
If nUBoundCells > nMaxUBoundCells Then
nMaxUBoundCells = nUBoundCells
End If
Next nRowIndex
' Create a 2D string array to contain the data in <avasCells>.
ReDim asCells(0 To nUBoundValue, 0 To nMaxUBoundCells)
' Copy all the data from avasCells() to asCells().
For nRowIndex = 0 To nUBoundValue
For nColumnIndex = 0 To UBound(avasCells(nRowIndex))
asCells(nRowIndex, nColumnIndex) = avasCells(nRowIndex)(nColumnIndex)
Next nColumnIndex
Next nRowIndex
SplitTo2DArray = asCells()
End Function

'Function to convert google formated date to MS Access date/time
Public Function GoogleToDate(GoogleDate As String) As Date
'2017-01-11T14:30:00-08:00 (Example of Google Date returned)
On Error GoTo ErrTrip
Dim NewDate As String
NewDate = Replace(GoogleDate, "T", " ")
NewDate = Mid(NewDate, 1, InStrRev(NewDate, "-") - 1)
GoogleDate = NewDate
ErrTrip_Exit:
Exit Function
ErrTrip:
GoogleDate = ""
Resume ErrTrip_Exit
End Function

'Function that converts MS Access Date/Time to Google format
Public Function DateToGoogle(ByVal MSDate As String, ByVal MSTime As String) As String
On Error GoTo ErrTrip
Dim NewDate As String
NewDate = CStr(Format(CDate(MSDate), "yyyy-mm-dd")) & "T" & CStr(Format(CDate(MSTime), "hh:nn:ss"))
DateToGoogle = NewDate
ErrTrip_Exit:
Exit Function
ErrTrip:
DateToGoogle = ""
Resume ErrTrip_Exit
End Function



Add a new Class module called: clsJSParse

I included a couple of functions to convert date time from Google format to MS Access format and vice versa.

When we receive responses from Google, they are formatted in JSON. The following class module is used to convert the JSON text into a two dimentional array with the results stored so you can easily access and process the results.

Class Module: clsJSParse


Option Compare Database
Option Explicit
'****************************************************************************
**************
'* RECURSIVE JSON TEXT PARSING CLASS *
'* *
'* Authour: Mark Timieski *
'* Date: 18OCT14 *
'* *
'* License: Code Project Open License (CPOL) 1.02 *
'* http://www.codeproject.com *
'* https://www.codeproject.com/Articles/828911...arser-for-Excel *
'* *
'* Description: *
'* Load JSON string through the 'Loadstring' method *
'* Then access the parsed keys and values through the 'key' and 'Value' properties *
'* *
'* Version: *
'* 1.0 Initial Release *
'* 1.1 Update to allow correct parse of 'number' type values of an object, these were *
'* previously ignored. Also updated to correct parse of 'number' type in the last *
'* element of an array, these were previously skipped. Added blStringArray and *
'* BlArrayEnd flags to manage the 'number' type in arrays *
'* *
'****************************************************************************
**************

Private strKey As Variant
Private strVal As Variant
Private intHMax As Integer
Private lngStatus As Long

Private Sub class_initialize()
lngStatus = -1
End Sub
Public Property Get err() As Long
'Status:
' 1 = JSON string has been sucessfully parsed
' -1 = JSON string has not been loaded, no results are available
' -2 = JSON string cannot be fully parsed (JSON text not fully or incorrectly formed)
err = lngStatus
End Property
Public Property Get NumElements() As Integer
NumElements = intHMax
End Property
Public Property Get key(Index As Integer) As Variant
If Index > UBound(strKey) Or Index < LBound(strKey) Then
key = ""
Else
key = strKey(Index)
End If
End Property
Public Property Get value(Index As Integer) As Variant
If Index > UBound(strVal) Or Index < LBound(strVal) Then
value = ""
Else
value = strVal(Index)
End If
End Property
Public Sub LoadString(JSonText As String)
'Load the JSON text into an array
Const cLongMax = (2 ^ 31) - 1 'Maximum Value for Long type
Dim lngIndex As Long
Dim lngContLoc As Long
Dim lngLoc As Long
Dim lngDelimitOffset As Long
Dim lngASize As Long
Dim intNoOfChecks As Integer 'Number of different control characters in JSON
Dim intCheck As Integer
Dim intCtrlChr As Integer
Dim intObJLvl As Integer
Dim intAryElement As Integer
Dim intLvl As Integer
Dim strID As String
Dim strChr As String
Dim strKeyValue As String
Dim strValue As String
Dim strPChar As String
Dim strFoundVal As String
Dim strTempString As String
Dim strAKey() As String
Dim strAVal() As String
Dim strALvlKey(100) As String
Dim blArray As Boolean 'Flag to indicate that an array has been found
Dim blStringArray As Boolean 'Flag to indicate that the element in the array is a string (added v1.1)
Dim BlArrayEnd As Boolean 'Flag to indicate that the end of an array is found (added v1.1)
Dim blValue As Boolean 'Falg to indicate that a value has been found
Dim blKeyAndValue As Boolean 'Found a key and value pair
Dim blDebug As Boolean
'Set the flag to true if you want to see debug information
'during the loading process
blDebug = False
On Error GoTo ErrHandler:
lngASize = 10
ReDim strAKey(lngASize)
ReDim strAVal(lngASize)
'intArrayElement = 1 'initialize value
'initialize values
blArray = False
BlArrayEnd = False '(added v1.1)
blStringArray = False '(added v1.1)
'Generate a string of control characters
'String is {[:,]}"
strID = ""
strID = strID & Chr(123) 'The '{' character
strID = strID & Chr(91) 'The '[' character
strID = strID & Chr(58) 'The ':' character
strID = strID & Chr(44) 'The ',' character
strID = strID & Chr(93) 'The ']' character
strID = strID & Chr(125) 'The '}' character
strID = strID & Chr(34) 'The '"' character
intNoOfChecks = Len(strID)
intObJLvl = 0
lngIndex = 1 'First element in the array will be strKey(1) and strVal(1)
'As we process the JSON string it becomes shorter and shorter, until
'its all been processed
Do While Len(JSonText) > 0
'Set to maximum value as default
lngContLoc = cLongMax
'Find Next control character:
'Scan the text for the closest control character
'to the beginning of the remaining JSON text
For intCheck = 1 To intNoOfChecks
strChr = Mid(strID, intCheck, 1)
lngLoc = InStr(1, JSonText, strChr, vbBinaryCompare)
If (lngLoc > 0) And (lngLoc < lngContLoc) Then
lngContLoc = lngLoc
intCtrlChr = intCheck
strPChar = strChr
End If
Next intCheck
'When the above for next loop ends we will have found the closest control character
'stored in intCtrlChr - an index (1 to 8) to the found character in strChr
'stored in lngContLoc - position of the next control character
'stored in strPChar - the closest next control character
If blDebug = True Then
Debug.Print "Parse Character: " & strPChar
End If
'A control character has been found, figure out what to do by the found character
If lngContLoc < cLongMax Then
'Capture the information before the control character
strValue = Mid(JSonText, 1, lngContLoc - 1)
'Capture everything after the control character (the remaining JSON string)
JSonText = Mid(JSonText, lngContLoc + 1, Len(JSonText))
Else
'We found the end of the JSON string
Exit Do
End If
'Found a number or boolean value or key (the comma)
'Updated in v1.1 to handle number types in array (process value as string or number; not both)
If (intCtrlChr = 4) Then
If ((blValue = True) Or (blArray = True)) And (blStringArray = False) Then
'Found a value, and we already have key
strFoundVal = fnStringToVal(strValue)
blKeyAndValue = True 'Set the "Key and value found" flag
End If
'Finding a comma resets the string found in the array
blStringArray = False
End If
'Start of object (The "{" character)
If intCtrlChr = 1 Then
intObJLvl = intObJLvl + 1
blArray = False 'An object, not an array
blValue = False 'Need to find a key first
If blDebug = True Then
Debug.Print "Start of Object, Moved up to level" & intObJLvl
End If
End If
'End of of object (The "}" character)
If intCtrlChr = 6 Then
'Updated in Revision 1.1
'Numbers preceded by the "}" character
If blValue = True Then
'Get the found value and set a flag
strFoundVal = fnStringToVal(strValue)
blKeyAndValue = True 'Set the "Key and value found" flag
'Add back a "}" character to the string so that the level can be decremented properly
JSonText = "}" & JSonText
Else
'No value was found, the "}" character indicates the end of this level
intObJLvl = intObJLvl - 1
blValue = False 'Need to find a key first
End If
If blDebug = True Then
Debug.Print "End of Object, Moved down to level" & intObJLvl
End If
End If
'Start of array (The "[" character)
If intCtrlChr = 2 Then
'intObJLvl = intObJLvl + 1
'strALvlKey(intObJLvl) = intArrayElement
blArray = True
blValue = True 'Next thing should be a value
intAryElement = 1
If blDebug = True Then
Debug.Print "Start of Array, Moved up to level" & intObJLvl
End If
End If
'End of of array (The "]" character)
If intCtrlChr = 5 Then
'Updated v1.1 parse last numeric or boolean value of an array
If (blArray = True) And (blStringArray = False) Then
'Get the found value and set a flag
strFoundVal = fnStringToVal(strValue)
blKeyAndValue = True 'Set the "Key and value found" flag
End If
BlArrayEnd = True 'Mark that the end of the array is found
blArray = False
blValue = False 'Need to find a key first
If blDebug = True Then
Debug.Print "End of Array, Moved down to level" & intObJLvl
End If
End If
'Object Value start is found (The ":" character)
If intCtrlChr = 3 Then
blValue = True
BlArrayEnd = False 'Added v1.1, start of an object value is not the end of an array
If blDebug = True Then
Debug.Print "ready to get value"
End If
End If
'Start of a string (the quote " character)
'Can be a key or value
If intCtrlChr = 7 Then
'The start of the key or value has been found
'The next quote will end the key or value
'(unless the quote has an escape character in front of it "\")
lngDelimitOffset = 1
Do
'Look for the next quote character
lngLoc = InStr(lngDelimitOffset, JSonText, Chr(34), vbBinaryCompare)
'If the string is zero length "" then exit the loop
If lngLoc = 1 Then
Exit Do
End If
'Check to see if there is a delimter just before the quote
'if there is then quote is part of the string and not the end of
'the string.
If Mid(JSonText, lngLoc - 1, 1) = Chr(92) Then
' The quote character has an escape character in front of it
'so this quote doesn't count. Remove the escape character.
JSonText = Mid(JSonText, 1, lngLoc - 2) & Mid(JSonText, lngLoc, Len(JSonText))
'and move the start of the check past the delimted quote
lngDelimitOffset = lngLoc
'If we have a boogered JSON string where there is no valid closing quotes
'the above "if" will cause an error (the MID statement will attempt to check
'the string starting at a position of -1) and the code will jump to the error
'handling section. If this error didn't occur the do..loop would get stuck.
Else
Exit Do
End If
Loop
'We now have a string, find any other delimiters
'(any delimited " characters have already been fixed)
strTempString = fnStringFix(Mid(JSonText, 1, lngLoc - 1))
If (blValue = True) Or (blArray = True) Then
'The key has been previously found and this is the value for the key
strFoundVal = strTempString
blKeyAndValue = True 'Set the "Key and value found" flag
If blArray = True Then
blStringArray = True 'Added v1.1, mark that the value is a string
End If
Else
If lngLoc > 0 Then
'We've found a key
strALvlKey(intObJLvl) = strTempString
If blDebug = True Then
Debug.Print "Found Key:" & strALvlKey(intObJLvl) & _
" for Level: " & intObJLvl
End If
End If
End If
JSonText = Mid(JSonText, lngLoc + 1, Len(JSonText))
End If
'Found a key and value, move it to the array
If blKeyAndValue = True Then
If lngIndex > lngASize Then
lngASize = lngASize + 100
ReDim Preserve strAKey(lngASize)
ReDim Preserve strAVal(lngASize)
End If
strAKey(lngIndex) = ""
For intLvl = 1 To intObJLvl
strAKey(lngIndex) = strAKey(lngIndex) & ">" & strALvlKey(intLvl)
Next intLvl
'Updated v1.1 - save last element of an array
If (blArray = True) Or (BlArrayEnd = True) Then
'add the array element to the key
strAKey(lngIndex) = strAKey(lngIndex) & ">" & Trim(Str(intAryElement))
'increment the array element
intAryElement = intAryElement + 1
'Reset end of array flag (set again when array end is found)
BlArrayEnd = False
End If
strAVal(lngIndex) = strFoundVal
If blDebug = True Then
Debug.Print "Added Key:" & strAKey(lngIndex) & " Value: " & strAVal(lngIndex) & " index: " & lngIndex
End If
lngIndex = lngIndex + 1 'Increment the array
blKeyAndValue = False 'Reset the "found" flag
blValue = False 'Reset the "Value Found" flag
End If
DoEvents
Loop
'Number of items found
intHMax = lngIndex - 1
strKey = strAKey
strVal = strAVal
lngStatus = 1 'JSON sucessfully parsed
Exit Sub
ErrHandler:
'Error handling code
lngStatus = -2 'JSON Parse error
'Uncomment the next line to figure out the cause of the issue
'Debug.Print VBA.err.Number
'Debug.Print VBA.err.Description
'Resume
End Sub

Private Function fnStringToVal(strInStr As String) As String
'Converts a string that contains formatting information into a string that only
'contains a value. Values can be text, integer, or floating point values.
'null is pssed back as a zero length string: "".
Dim intStrPos As Integer
Dim strTemp As String
Dim intChar As Integer
'default value
strTemp = ""
'Make sure that the string does not have a zero length
strInStr = " " & strInStr
'Loop through each chracter in the string and remove anything
'that is not alphanumeric.
For intStrPos = 1 To Len(strInStr)
intChar = Asc(Mid(strInStr, intStrPos, 1))
If ((intChar >= Asc("a")) And (intChar <= Asc("z"))) Or _
((intChar >= Asc("A")) And (intChar <= Asc("Z"))) Or _
((intChar >= Asc("0")) And (intChar <= Asc("9"))) Or _
(intChar = Asc(".")) Or (intChar = Asc("+")) Or (intChar = Asc("-")) Then
strTemp = strTemp & Chr(intChar)
End If
Next intStrPos
'Values that are listed as 'null' are converted to a zero length string
If InStr(1, "null", strTemp, vbTextCompare) > 0 Then
strTemp = ""
End If
fnStringToVal = strTemp
End Function

Private Function fnStringFix(strInput As String) As String
'This function goes through a JSON string and corrects delimited characters
Dim blParseComplete As Boolean
Dim lngStartPos As Long
Dim lngCurrentPos As Long
blParseComplete = False
lngStartPos = 1
Do While blParseComplete = False
blParseComplete = True 'If we don't find any escape sequences then allo the loop to end
'Escaped sequence: replace \\ with \
lngCurrentPos = InStr(lngStartPos, strInput, "\\", vbTextCompare) 'look for the the specific escape sequence
If lngCurrentPos > 0 Then
strInput = Mid(strInput, 1, lngCurrentPos - 1) & "\" & _
Mid(strInput, lngCurrentPos + 2, Len(strInput))
blParseComplete = False 'set the status to check for another escape
End If
'Escaped sequence: replace \/ with /
lngCurrentPos = InStr(lngStartPos, strInput, "\/", vbTextCompare) 'look for the the specific escape sequence
If lngCurrentPos > 0 Then
strInput = Mid(strInput, 1, lngCurrentPos - 1) & "/" & _
Mid(strInput, lngCurrentPos + 2, Len(strInput))
blParseComplete = False 'set the status to check for another escape
End If
'Escaped sequence: replace \b with a backspace
lngCurrentPos = InStr(lngStartPos, strInput, "\b", vbTextCompare) 'look for the the specific escape sequence
If lngCurrentPos > 0 Then
strInput = Mid(strInput, 1, lngCurrentPos - 1) & Chr(8) & _
Mid(strInput, lngCurrentPos + 2, Len(strInput))
blParseComplete = False 'set the status to check for another escape
End If
'Escaped sequence: replace \f with a formfeed
lngCurrentPos = InStr(lngStartPos, strInput, "\f", vbTextCompare) 'look for the the specific escape sequence
If lngCurrentPos > 0 Then
strInput = Mid(strInput, 1, lngCurrentPos - 1) & Chr(12) & _
Mid(strInput, lngCurrentPos + 2, Len(strInput))
blParseComplete = False 'set the status to check for another escape
End If

'Escaped sequence: replace \n with a newline
lngCurrentPos = InStr(lngStartPos, strInput, "\n", vbTextCompare) 'look for the the specific escape sequence
If lngCurrentPos > 0 Then
strInput = Mid(strInput, 1, lngCurrentPos - 1) & Chr(10) & _
Mid(strInput, lngCurrentPos + 2, Len(strInput))
blParseComplete = False 'set the status to check for another escape
End If
'Escaped sequence: replace \r with a carriage return
lngCurrentPos = InStr(lngStartPos, strInput, "\r", vbTextCompare) 'look for the the specific escape sequence
If lngCurrentPos > 0 Then
strInput = Mid(strInput, 1, lngCurrentPos - 1) & Chr(13) & _
Mid(strInput, lngCurrentPos + 2, Len(strInput))
blParseComplete = False 'set the status to check for another escape
End If
'Escaped sequence: replace \t with a horizontal tab
lngCurrentPos = InStr(lngStartPos, strInput, "\t", vbTextCompare) 'look for the the specific escape sequence
If lngCurrentPos > 0 Then
strInput = Mid(strInput, 1, lngCurrentPos - 1) & Chr(9) & _
Mid(strInput, lngCurrentPos + 2, Len(strInput))
blParseComplete = False 'set the status to check for another escape
End If
'Escaped sequence: replace \uXXXX with a unicode character
lngCurrentPos = InStr(lngStartPos, strInput, "\u", vbTextCompare) 'look for the the specific escape sequence
If lngCurrentPos > 0 Then
strInput = Mid(strInput, 1, lngCurrentPos - 1) & _
ChrW$(CLng("&h" & Mid(strInput, lngCurrentPos + 2, 4))) & _
Mid(strInput, lngCurrentPos + 6, Len(strInput))
blParseComplete = False 'set the status to check for another escape
End If
Loop
fnStringFix = strInput
End Function


Support module for clsJSParse class

Add a new module and add the following code:


'Takes JSON data from response text and converts into an array
Public Function JsonParse(JSonText As String) As String
Dim intindex As Integer
Dim strVar As String
Dim strStatus As String
Dim strErrStatus As String
Dim intDebugLevel As Integer
Dim strJsonToCSV As String
Dim a As Long
'0 = no info
'1 = debug.print status info
'Change value for debug
intDebugLevel = 0
'Set up the class connection
Dim clsJSON As clsJSParse
Set clsJSON = New clsJSParse
If intDebugLevel > 0 Then
Debug.Print "Status before load: " & clsJSON.err
End If
'The JSON information will then be available through the clsJSON.Key and clsJSON.Value data pairs
clsJSON.LoadString JSonText
If intDebugLevel > 0 Then
Debug.Print "Status after load: " & clsJSON.err
Debug.Print "Loaded: " & clsJSON.NumElements & " elements."
End If
strStatus = "Loaded: " & clsJSON.NumElements & " Elements"
'Convert status data to text
Select Case clsJSON.err
Case -1
strErrStatus = "JSON string not loaded."
Case -2
strErrStatus = "JSON string cannot be fully parsed."
Case 1
strErrStatus = "JSON string has been parsed."
End Select
'Display status data
strStatus = strStatus & Chr(10) & strErrStatus & Chr(10)
If intDebugLevel > 0 Then
Debug.Print strStatus
End If
'Get all the elements of the parsed JSON text
For intindex = 1 To clsJSON.NumElements
strJsonToCSV = strJsonToCSV & intindex & ";" & clsJSON.key(intindex) & ";" & clsJSON.value(intindex) & vbNewLine
Next intindex
JsonParse = strJsonToCSV
End Function

Below are a few examples of different options on getting/sending data to Google calendar. The JSON text is also formatted for you, all you have to do is replace values with your own. There are other parameters available. I included most used. Refer to reference listed at top of this article for complete reference.

Get Calendar List
Dim Method As String, content As String, Body As String, addAuth As Boolean, URL As String
Dim a As Long, JsonArray() As String, JSonText As String
If CheckToken = False Then DoCmd.OpenForm "Browser"
Method = "GET"
'GET https://www.googleapis.com/calendar/v3/users/me/calendarList
URL = "https://www.googleapis.com/calendar/v3/users/me/calendarList"
content = ""
Body = ""
addAuth = True
SendHTTP URL, Method, content, Body, addAuth
JSonText = JsonParse(HTTP_ResponseText)
JsonArray() = SplitTo2DArray(JSonText, vbNewLine, ";")
For a = LBound(JsonArray) To UBound(JsonArray)
Debug.Print a, JsonArray(a, 1), JsonArray(a, 2) 'Now in an array where you can use it
Next a

Get Calendar Rules
Dim Http As MSXML2.XMLHTTP60
Dim hdrLine As Variant
Dim hdrarr As Variant
Dim Method As String, content As String, Body As String, addAuth As Boolean, Headers As Variant, URL As String
Dim a As Long, JsonArray() As String, JSonText As String
Dim CalID As String
If CheckToken = False Then DoCmd.OpenForm "Browser"
Method = "GET"
'GET https://www.googleapis.com/calendar/v3/cale.../calendarId/acl
URL = "https://www.googleapis.com/calendar/v3/calendars/primary/acl"
content = ""
Body = ""
addAuth = True
SendHTTP URL, Method, content, Body, addAuth
JSonText = JsonParse(HTTP_ResponseText)
JsonArray() = SplitTo2DArray(JSonText, vbNewLine, ";")
For a = LBound(JsonArray) To UBound(JsonArray)
Debug.Print a, JsonArray(a, 1), JsonArray(a, 2) 'Now in an array where you can use it
Next a

Create Secondary Calendar
Dim Method As String, content As String, Body As String, addAuth As Boolean, URL As String
Dim a As Long, JsonArray() As String, JSonText As String
If CheckToken = False Then DoCmd.OpenForm "Browser"
Method = "POST"
URL = "https://www.googleapis.com/calendar/v3/calendars"
content = "application/json"
Body = "{""timeZone"":""America/Los_Angeles"",""description"":""Description of Calendar"",""summary"":""Name of Calendar""}"
addAuth = True
SendHTTP URL, Method, content, Body, addAuth
JSonText = JsonParse(HTTP_ResponseText)
JsonArray() = SplitTo2DArray(JSonText, vbNewLine, ";")
For a = LBound(JsonArray) To UBound(JsonArray)
Debug.Print a, JsonArray(a, 1), JsonArray(a, 2)
Next a

Add event
Dim Method As String, content As String, Body As String, addAuth As Boolean, URL As String
Dim a As Long, JsonArray() As String, JSonText As String
If CheckToken = False Then DoCmd.OpenForm "Browser"
Method = "POST"
'POST https://www.googleapis.com/calendar/v3/cale...lendarId/events
URL = "https://www.googleapis.com/calendar/v3/calendars/primary/events" 'primary is your main calendar you you can use the name of a secondary calendar
content = "application/json"
Body = "{""start"":{""dateTime"":""2017-01-11T14:30:00"",""timeZone"":""America/Los_Angeles""},""end"":{""dateTime"":""2017-01-11T17:30:00"",""timeZone"":""America/Los_Angeles""},""colorId"":""11"",""summary"":""API Test 2"",""description"":""test 3"",""location"":""here""}"
addAuth = True
SendHTTP URL, Method, content, Body, addAuth
JSonText = JsonParse(HTTP_ResponseText)
JsonArray() = SplitTo2DArray(JSonText, vbNewLine, ";")
For a = LBound(JsonArray) To UBound(JsonArray)
Debug.Print a, JsonArray(a, 1), JsonArray(a, 2)
Next a

Delete calendar
Dim Method As String, content As String, Body As String, addAuth As Boolean, URL As String
Dim a As Long, JsonArray() As String, JSonText As String
If CheckToken = False Then DoCmd.OpenForm "Browser"
Method = "DELETE"
'DELETE https://www.googleapis.com/calendar/v3/calendars/calendarId

URL = "https://www.googleapis.com/calendar/v3/calendars/NameOfYourCalendar@group.calendar.google.com"
content = "application/json"
Body = ""
addAuth = True
SendHTTP URL, Method, content, Body, addAuth
Debug.Print HTTP_Status

Delete event
Dim Method As String, content As String, Body As String, addAuth As Boolean, URL As String
Dim a As Long, JsonArray() As String, JSonText As String
If CheckToken = False Then DoCmd.OpenForm "Browser"
Method = "DELETE"
'DELETE https://www.googleapis.com/calendar/v3/cale.../events/eventId
URL = "https://www.googleapis.com/calendar/v3/calendars/primary/events/mftsj55h4vm1akppalfxyz3fsk"
content = "application/json"
Body = ""
addAuth = True
SendHTTP URL, Method, content, Body, addAuth
If HTTP_Status >= 400 Then
MsgBox "gone"
Exit Sub
End If
'http_status = 204 when deleted
'http_status = 410 gone

Update event
Dim Method As String, content As String, Body As String, addAuth As Boolean, URL As String
Dim a As Long, JsonArray() As String, JSonText As String
If CheckToken = False Then DoCmd.OpenForm "Browser"
Method = "PUT"
'PUT https://www.googleapis.com/calendar/v3/cale.../events/eventId
URL = "https://www.googleapis.com/calendar/v3/calendars/primary/events/mftsj55h4vm1akppalfxyz3fsk"
content = "application/json"
Body = "{""start"":{""dateTime"":""2017-01-11T14:30:00"",""timeZone"":""America/Los_Angeles""},""end"":{""dateTime"":""2017-01-11T16:30:00"",""timeZone"":""America/Los_Angeles""},""colorId"":""10"",""summary"":""API Final"",""description"":""test 4"",""location"":""there""}"
addAuth = True
SendHTTP URL, Method, content, Body, addAuth
If HTTP_Status >= 400 Then
MsgBox "error"
Exit Sub
End If
JSonText = JsonParse(HTTP_ResponseText)
JsonArray() = SplitTo2DArray(JSonText, vbNewLine, ";")
For a = LBound(JsonArray) To UBound(JsonArray)
Debug.Print a, JsonArray(a, 1), JsonArray(a, 2)
Next a




Attached is a list of colorId for events.
One other thing to keep in mind is that all text in your JSON that is sent to Google is CASE SENSITIVE so be careful.

A lot of time and effort was done to create the above. There was no "one stop shop" that really explained the whole Google Calendar Integration that I have fould. Many of the searches that I did started in one direction then stopped and most of the code simply did not work. I hope it helps you as much as it has helped me. Thanks to all of the supporters of the forums and you help in answering questions and getting us coders on the right track. This is my contribution and thanks to all the help others have given me.

For a complete listing of timezones go to https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
Thanks to Johan Godfried for his article on the OAuth process.
http://stackoverflow.com/questions/3960985...016-application
Also thanks to 'Tim Ski for the JSON parsing class.
https://www.codeproject.com/Articles/828911...arser-for-Excel
Attached File(s)
Attached File  Google_Calendar_Event_Color_Chart.PNG ( 12.32K )Number of downloads: 36
Attached File  Google_Calendar_Instructions.rtf ( 1.3MB )Number of downloads: 126
 
Go to the top of the page
 
DanielPineault
post Jan 12 2017, 08:44 PM
Post#2


UtterAccess VIP
Posts: 6,831
Joined: 30-June 11



Very nice of you to share. hat_tip.gif

Why not post it, move it to the Code Archive? Probably best to package everything up in a demo database.
Go to the top of the page
 
gtsolano
post Jan 12 2017, 08:47 PM
Post#3



Posts: 359
Joined: 21-January 06



I tried to post there but once I posted I could not find it again. Not sure of the process of moving to archive.

Thanks for the comment.
thumbup.gif
Go to the top of the page
 
theDBguy
post Jan 12 2017, 09:25 PM
Post#4


Access Wiki and Forums Moderator
Posts: 76,290
Joined: 19-June 07
From: SunnySandyEggo


Hi GT,

Your Code Archive submission was received but it won't be visible until it's approved.

Please give it some time.

Thank you,
DBG
Go to the top of the page
 
gtsolano
post Jan 12 2017, 10:04 PM
Post#5



Posts: 359
Joined: 21-January 06



Attached is a sample database with all the code.

Be sure to get your client_id and client_secret and enter in the table first. The examples form has several different items for you to play with. You may need to change out the id for your specific calendar item for them to work correctly.

When you create a calendar event the id will display in the window.

Hope this helps.

Thanks
Attached File(s)
Attached File  GoogleCalendarSample.zip ( 116.4K )Number of downloads: 186
 
Go to the top of the page
 
zocker
post Jan 12 2017, 11:55 PM
Post#6


Utterly Eccentric and Moderator
Posts: 4,069
Joined: 4-March 00
From: Bristol / Ipswich / Spain


Wonderful post, a huge amount of work, a big thank you!

Zocker
Go to the top of the page
 
asimze
post Jan 13 2017, 03:56 AM
Post#7



Posts: 164
Joined: 12-December 12
From: Bosna i Hercegovina


My dear mr. gtsolano, i haven't word to express my gratitude! I spent hundreds hours to solve google calendar, and final solution is here.
Without false compliment this is HISTORICAL day for Access society. Your solution will help us to give new life to Access business application, to touch GOOGLE, without new charge.
What now? I hope administrators will start some promotion to encourage and less experience developers to use Your work, and upgrade your application to new level. At least promotion mail to all developers in list.
Good luck and good health to You!
Asim
Go to the top of the page
 
shadow
post Jan 15 2017, 04:52 PM
Post#8



Posts: 280
Joined: 19-April 04
From: Toronto


I agree. This is fantastic and really opens up opportunities to Access developers. As others have said, there's no known resource that puts it all together so clearly.

Has anyone been able to get the sample database to work? I put in about an hour and a half with no luck. I must be missing something.

- I got my Client_ID and Client_secret and entered them into the table
- I pressed CTRL+G and used the immediate window to call LoadCalendarInfo, which I understand should have set the global variables.
- I then opened the browser which let me log onto google and click the Allow button, which should allow me to use the interface to control the calendar.

My understanding is that this captures tokens. However the code then went into debug mode and I got an error "The system cannot locate the resource specified.". When I click Debug it points to a command in the function SendHTTP where it says "Call .send(Body)"

I am hoping that someone has succeeded in getting further than me and knows why I am encountering this. Maybe I missed a step in the instructions?

Thank you

SHADOW
Go to the top of the page
 
shadow
post Jan 15 2017, 06:08 PM
Post#9



Posts: 280
Joined: 19-April 04
From: Toronto


Another important question:

Since this is the first I've read about utilizing Google services, I know close to nothing about the methods employed so I'm basically trying to follow along the example.

The first step requires creating a project with my account and then retrieving a client_id and client_token. I assume that these will allow me to access MY calendar.

Can I create applications that will let people access THEIR calendar? Would people using such applications have to register for Google APIs and create a project and get their own client_id and client_token or will my own client_id and client_token work for other calendars?

SHADOW
Go to the top of the page
 
gtsolano
post Jan 18 2017, 03:14 PM
Post#10



Posts: 359
Joined: 21-January 06



Hello All...

I have attached an updated example. I added a couple lines of code that should fix the issue.

- When you get your credentials from Google, make sure to remove the extra spaces in front and behind your client_id and client_secret. For some reason when you click the copy from Google, it adds spaces.
- Make sure your scope reads https://www.googleapis.com/auth/calendar in the table
- When you run for the first time, open the browser form first. The browser window will ask you to log into your account that the id and secret are attached to. Once you complete this and allow it to use, it will get your access/refresh tokens.

Once done you can then run the examples. For Remove Calendar, Delete Event, and Update Event, you must alter the code to change the EventID to an actual event that you have created. The other items should work by clicking on them with out any changes.

The client_id and client_secret are unique to each calendar account. You will be able to access you primary and any secondary calendars attached to that Google account. If you want to use a different calendar account, you must use their id and secret info.

Hope this helps!
Attached File(s)
Attached File  GoogleCalendarSample2.zip ( 100.56K )Number of downloads: 162
 
Go to the top of the page
 
shadow
post Jan 18 2017, 03:35 PM
Post#11



Posts: 280
Joined: 19-April 04
From: Toronto


Fantastic! I'll give it a shot as soon as possible.

The client_id and client_secret are unique to each calendar account. You will be able to access you primary and any secondary calendars attached to that Google account. If you want to use a different calendar account, you must use their id and secret info.

What I was hoping was that people who use my scheduling module that I made in Access will be able to integrate with Google Calendar so they can get it on their phones. (As an aside, I've looked high and low for a way to integrate with iPhone or Android calendar but no luck) What I am hoping to do is create an app that reads in the my schedule's table and synchs it with the user's google calendar at an interval stated by the user. What I had HOPED was that all they need to do is enter their gmail address and password, and then a path to the database and the app takes over from there. But it seems that they have to register with Google developer and so on to get the id/secret. Am I understanding correctly?

SHADOW
Go to the top of the page
 
gtsolano
post Jan 18 2017, 03:41 PM
Post#12



Posts: 359
Joined: 21-January 06



When you create a scheduled event via MS Access module as outlined, it will post to Google calendar. Anyone or any device that used the Google calendar app (phone, web calendar, etc) will be updated immediately when you post the event. They don't have to get any client/secret in order to see the events. You only need these when using the MSA module.

In order for them to use the MSA app, they do need to get the client/secret info. This is for the OAuth that Google required to access/modify the calendar. Once you activate once you won't have to do it again though unless they change/reset their secret or ids.
Go to the top of the page
 
shadow
post Jan 18 2017, 03:47 PM
Post#13



Posts: 280
Joined: 19-April 04
From: Toronto


I tried with the new demo project and clicking "allow" gives me the same error as last time: The system cannot locate the resource specified.". When I click Debug it points to a command in the function SendHTTP where it says "Call .send(Body)"

Am I using the demo wrong?

SHADOW
Go to the top of the page
 
gtsolano
post Jan 26 2017, 12:37 PM
Post#14



Posts: 359
Joined: 21-January 06



What version of MS Access are you using?
Go to the top of the page
 
shadow
post Jan 27 2017, 10:00 AM
Post#15



Posts: 280
Joined: 19-April 04
From: Toronto


2010

SHADOW
Go to the top of the page
 
gtsolano
post Jan 31 2017, 10:48 AM
Post#16



Posts: 359
Joined: 21-January 06



This is written in 2016. I tried to open in 2007 and would not open. File formats are different apparently between the versions. 2016 also has different, more updated, web browser control. Newer control as the On Navigate Error event that does not exist on the older versions. I have not tried with 2010. Also references to Microsoft XML v6.0 are different in lower versions which may need to be modified.
Go to the top of the page
 
shadow
post Jan 31 2017, 10:44 PM
Post#17



Posts: 280
Joined: 19-April 04
From: Toronto


So how can this be modified to work in other versions...?

SHADOW
Go to the top of the page
 
asimze
post Feb 8 2017, 02:57 AM
Post#18



Posts: 164
Joined: 12-December 12
From: Bosna i Hercegovina


mr. Solano
I was trying Your Google calendar application and have problem.
Ms Access 2010!!!
After opening Browse and starting OAuth2, he send me huge code, something like writing web form and don't continue. Newer get token.
What to do?
Asim
Go to the top of the page
 
Flap Zappa
post Aug 9 2018, 05:19 AM
Post#19



Posts: 7
Joined: 8-August 18



thanks for all the excellent work. I have been looking for ages for a solution to this. I have a couple of questions though. I am using it to keep track of rentals on an apartment I have and I only need it as multi day events and I do not need the time part of the datetime field, and cannot find out how to set that option, and secondly I would like to add an email reminder a couple of days before the event

I am guessing that they would need to be added to this line
CODE
    'Body = "{""start"":{""dateTime"":""2018-09-20T12:00:00"",""timeZone"":""America/Los_Angeles""},""end"":{""dateTime"":""2018-09-21T12:00:00"",""timeZone"":""America/Los_Angeles""},""colorId"":""11"",""summary"":""API Test 2"",""description"":""test 3"",""location"":""here""}"
Go to the top of the page
 
theDBguy
post Aug 9 2018, 09:49 AM
Post#20


Access Wiki and Forums Moderator
Posts: 76,290
Joined: 19-June 07
From: SunnySandyEggo


Hi Flap,

Welcome to UtterAccess!
welcome2UA.gif

Does this mean you are not experiencing the same problems with the demo as the others have reported earlier?
Go to the top of the page
 
3 Pages V  1 2 3 >


Custom Search


RSSSearch   Top   Lo-Fi    15th September 2019 - 03:12 PM