0

I am working on a database for our company. One of the big things they want this database to do is to create reminders and emails based on changed fields and newly created records. For example, when the user puts a date in the First_Meeting field, an event should be triggered that will create 3 reminders on an Outlook Calendar. As a second example, when a new record is created in the Contract table, an event should be triggered to create 2 reminders in an Outlook Calendar and 2 Outlook emails.

I have the logic to do all of this, but I am trying to figure out the best way to handle the events. It is important that the trigger happens on whatever form the First_Meeting field is updated. If I do a form field event, I have to make sure I add the code to all forms that include that field. I am wondering if there is a way to do this with Class modules so that I could fire an event on a table field or record. I have not done any OO, but looked into it a little bit years ago, so I have a very vague understanding of how it works. I apologize that my question is somewhat non-specific, but I don’t want to spend a lot of time on the learning curve of OO & Class Modules only to find out that what I am trying to do cannot be done. On the other hand, if I could do all of this in one place and not have to worry about it going forward that would be well worth any time spent!

My question is: Can I create a class on a table field that would fire an event anytime that field is edited? And can I create a class on a table (or table record) that would fire any time there is a record inserted into the table? What is the logic to accomplish this?

I am using a table to hold all of the items that will be created based on the field that is updated, or record that is created.

I am using Access 2016. Thanks in advance for any help you can give me!!! Kim

This is the event code I am currently using for the First_Meeting Event:

'This code calls a form to select the reminders to create

Private Sub First_Meeting_AfterUpdate()
Dim strSql As String
Dim strWhere As String
Dim strOrderBy As String
Dim intRecordCount As Integer

'Save any changes to data before selecting appointments to set
If Me.Dirty Then
    Me.Dirty = False
End If

'The "Where" keyword is not included here so it can be used for the DCount function
strWhere = " [Appt Defaults].[Field Name]='First Meeting Date'"
strOrderBy = " ORDER BY [Appt Defaults].[Order for List], [Appt Defaults Child].[Date Offset]"

strSql = "SELECT Count([Appt Defaults Child].ID) AS CountOfID " & _
    "FROM [Appt Defaults] INNER JOIN [Appt Defaults Child] ON [Appt Defaults].ID = [Appt Defaults Child].ReminderID"

intRecordCount = DCount("ReminderID", "qDefaultAppts", strWhere)

If intRecordCount > 0 Then

    DoCmd.SetWarnings False
    'Delete records from the Temp table
    DoCmd.RunSQL "Delete * From TempApptToSelect"

    'Add the "Where" keyword to be used in the query
    strWhere = "Where " & strWhere
    strSql = CurrentDb.QueryDefs("[qAddApptsToTemp-MinusCriteria]").SQL
    'The ";" symbol is added to the end of the query so it needs to be stripped off
    strSql = Replace(strSql, ";", "")
    strSql = strSql & strWhere & strOrderBy
    DoCmd.RunSQL strSql
    'Flag all of the events in the Temp Table as Selected
    DoCmd.RunSQL "UPDATE TempApptToSelect SET TempApptToSelect.IsSelected = -1"
    DoCmd.SetWarnings True

    DoCmd.OpenForm "Reminders - Select Main", , , , , , OpenArgs:=Me.Name

End If
End Sub

'This code is from the form where the reminders are selected

Private Sub cmdCreateReminders_Click()
' This Routine copies all of the selected default records from the Appt Defaults tables and copies them to the Reminder Tables
'
Dim rstReminderDefaults As Recordset
Dim rstReminders As Recordset
Dim nID As Integer
Dim dtStartDate As Date
Dim dtStartTime As Date
Dim dtEndTime As Date
Dim strProjectName As String
Dim strProjectAddress As String
Dim strApptArea As String
Dim iCount As Integer

' The calling form has the info needed to set the values for the reminders
' The form "frmCalendarReminders" is generic and will be on all forms that need to set reminders

txtCallingForm = Me.OpenArgs()

'The form recordset is a temp query created from the calling routine which determines the record filter
Set rstReminders = Forms(txtCallingForm)!frmCalendarReminders.Form.RecordsetClone
Set rstReminderDefaults = CurrentDb.OpenRecordset("qApptsToSet")

nID = Forms(txtCallingForm)!ID

strApptArea = Left(rstReminderDefaults![Appt Area], 8)

Select Case strApptArea
    Case "Projects"

        strProjectName = Forms(txtCallingForm)!txtProjectName
        strProjectAddress = Forms(txtCallingForm)!txtProjectAddressLine & vbCrLf & Forms(txtCallingForm)!txtProjectCityLine

        With rstReminderDefaults
            Do While Not .EOF
                'If this reminder has not already been created
                If DCount("ID", "PR_Child-Reminders", "[Project ID] =" & Forms(txtCallingForm)![ID] & " And [ReminderChildID]= " & ![ReminderChildID]) = 0 Then
                    rstReminders.AddNew
                    'Initialize fields with values from defaults
                    rstReminders![ReminderChildID] = ![ReminderChildID]
                    rstReminders![Project ID] = nID
                    rstReminders![Reminder Type] = ![Outlook Item Type]
                    rstReminders![Reminder Subject] = ![Subject]
                    rstReminders![Reminder Text] = ![Body]
                    rstReminders![Invited] = ![Invite]
                    rstReminders![Email CC] = ![Email CC]
                    rstReminders!Calendar = !CalendarID
                    rstReminders!Color = !ColorID
                    Select Case ![Appt Type]
        .
        .
                        Case "First Meeting"
                            If Not IsNull(Forms(txtCallingForm)!dtFirstMeeting) Then
                                'dtStartDate will be used later to fill in Placeholder field in Subject and Body of Calendar and Email Items
                                 dtStartDate = Forms(txtCallingForm)!dtFirstMeeting
                                 rstReminders![Reminder Date] = dtStartDate + ![Date Offset]
                            Else
                                'Quit working on this reminder since it has invalid conditions
                                MsgBox "No date has been set for the " & ![Appt Type] & " so reminders cannot be created"
                                rstReminders.CancelUpdate
                                GoTo NextLoop
                            End If
                    End Select
                        .
          rstReminders.Update
                    CreateOrSend (txtCallingForm)
           .            
NextLoop:
                .MoveNext
            Loop
        End With
End Select
DoCmd.Close

End Sub

‘This code is used to create the reminder or email

Sub CreateOrSend(CallingForm)
Dim bError As Boolean
Dim strName As String
Dim strSubject As String
Dim strBody As String
Dim strType As String
Dim strAttendees As String
Dim strCC As String
Dim strColorCategory As String
Dim dtStartDate As Date
Dim dtEndDate As Date
Dim strReminderText As String
Dim strLocation As String
Dim decDuration As Single

With Forms(CallingForm)!frmCalendarReminders.Form
    'bError will be used to determine if the calendar item is created without error
    bError = False
    If !cmbReminderType = "Calendar" Then
        strName = !cmbCalendar.Column(2)
        strSubject = !txtReminderSubject
        If Not IsNull(!txtReminderNote) Then
            strBody = !txtReminderNote
        Else
            strBody = ""
        End If
        If Not IsNull(!txtInvite) Then
            strAttendees = !txtInvite
        Else
            strAttendees = ""
        End If
        strColorCategory = !cmbColor.Column(1)
        dtStartDate = !dtStartDate & " " & !dtStartTime
        dtEndDate = !dtEndDate & " " & !dtEndTime
        If Not IsNull(!txtReminderNote) Then
            strReminderText = !txtReminderNote
        Else
            strReminderText = ""
        End If
        strLocation = IIf(IsNull(.Parent!txtProjectAddressLine), ".", .Parent!txtProjectAddressLine & ", " & .Parent![Project City])
        ' Parameter Order: strName, strSubject, strBody, strAttendees, strColorCategory, dtStartDate, dtEndDate, strReminderText Optional:  strLocation, decDuration
        Call CreateCalendarAppt(bError, strName, strSubject, strBody, strAttendees, strColorCategory, dtStartDate, dtEndDate, strReminderText, strLocation)

        If bError = False Then
            !dtCreatedItem = Date
        Else
            MsgBox "***** YOUR APPOINTMENT FAILED ******"
        End If
    Else
        If Not IsNull(!txtReminderNote) Then
            strBody = !txtReminderNote
        Else
            strBody = ""
        End If
        strSubject = !txtReminderSubject
        If Not IsNull(!txtInvite) Then
            strAttendees = !txtInvite
            strCC = !txtEmailCC
            SendCustomHTMLMessages strAttendees, strCC, strSubject, strBody
            !dtCreatedItem = Date
        Else
            MsgBox "There were no email addresses to send this message to"
        End If

    End If
End With
End Sub
ComputerVersteher
  • 2,638
  • 1
  • 10
  • 20
Kim
  • 11
  • 6

2 Answers2

0

Unfortunately, there is no way to accomplish what you want. Although Access has something like "Data Macros", there is no way to to run a VBA procedure from there.

But don't be afraid of using event procedures in your forms. You don't have to copy all your existing code to each and every event procedure. You can place the existing code in a standard module, and in the forms, use very short event procedures that call these procedures in the standard modules. This still makes the main routines easy to maintain.

Wolfgang Kais
  • 4,010
  • 2
  • 10
  • 17
  • 1
    You can actually run VBA from data macros, see [this answer](https://stackoverflow.com/a/12355344/7296893). It's just generally a bad idea, because it will error if you're using external programs to work with Access. – Erik A Aug 24 '18 at 07:25
  • Thanks for the update, @Erik. I thought of something like this, looked into it and found that the custom functions are not available in *expression builder*. But you are right, I just tested it and you can indeed enter a custom function name. – Wolfgang Kais Aug 24 '18 at 08:36
0

I don't agree with Wolfgang.

Of course I would suggest using MSSQL Server as backend, but with Access and the Data-Macros you can update a timestamp field in the underlying tables that updates on every change.

In addition run a script on a server (I don't knpw what intervall would be sufficent for you) every x minutes and check if row was updated since last run of script (compare timestamp)..

If true run your tasks.

If this is not an option we can talk about intercepting form-events with a class and WithEvents but this will need more effort to implement.

ComputerVersteher
  • 2,638
  • 1
  • 10
  • 20
  • I am going to be moving to SQL Sever in the next couple of weeks, but I don't think scripting from the back-end would work. It needs to be very user interactive, as the user needs to select which reminders to send and may make changes before the reminder is actually created. – Kim Aug 24 '18 at 04:16
  • Do I miss that part of code? Remainder edits have to be made just after data change (e.g 1sec) or can they be done some time after (e.g 1-5min)? Dumping Access BE is a great idea! Give you much mire options. – ComputerVersteher Aug 24 '18 at 04:23
  • MySQL or MS SQL Server? – ComputerVersteher Aug 24 '18 at 04:31
  • I will be moving to MySQL. The reminders are created on the AfterUpdate event which is the first section of code above (which callls the other sections.) The reminders do not technically need to be done immediately after, but I think it would be a little distracting if the user was doing something else and a form popped up for them to edit the reminders. – Kim Aug 24 '18 at 04:31
  • Did you evaluate the pros and cons? You know MariaDB? – ComputerVersteher Aug 24 '18 at 04:36
  • I have not evaluated pros and cons, just looking for a cheap option since I can get MySQL with my web hosting service. I have another database on Azure, but was hoping not to have a big monthly expense with this one. I have not heard of MariaDB, but would be happy to get any input before I make the change. – Kim Aug 24 '18 at 04:41
  • Web hosted server is tricky. 1. you need a fast connection or you have be thrifty with data, what means a complete rewrite of your fontend. 2. webservers can be accessed by everyone, so they need to be secured! Do you host your Access backend online? Arguments for MSSql Server: windows-user authentification (with MySQL only on enterprise edition), auditlog (the same) and OLEDB Driver for ADO. and the express ediion is for free too. But of course MySQL is far above Access backend (I use it too at work), but its from Oracle, MariaDB is compatoble to MySQL and I plan to move there. – ComputerVersteher Aug 24 '18 at 04:58
  • I am rethinking doing this on the web. It could be hosted on our network if I could find a back-end like MySQL that I could set up there. I don't really need to do this on the web, just make it available to remote users, but they have the capability of logging into our servers. That is probably a much better option. I will check out MariaDB as a possibility for my back-end.Thanks for the input!!! – Kim Aug 24 '18 at 05:09
  • Use a VPN and a terminalserver/remoteapp. That keeps things fast and simple without many changes, even on slow connections.. – ComputerVersteher Aug 24 '18 at 05:22
  • Back to topic: Do you need to execute your code on any chages of the related forms or just some controls (how many)? if that count is below 10 we can go with Wolfgang and put the code in the eventhandlers. Otherwise you need an event sink and I have to look for code ( got an example somewhere and I need that too for auditlog on form level. – ComputerVersteher Aug 24 '18 at 05:28
  • 1
    Imo if you're working on a large project, going with classes and event handlers might initially add some complexity, but can certainly end up being simpler in the end, since you're not repeating code all the time, making it easier to add and change functionality. I have examples scattered around on some answers, for example, [this one](https://stackoverflow.com/a/51894855/7296893) – Erik A Aug 24 '18 at 07:35
  • @ErikvonAsmuth I totally agree on the benefit. My form audit has code in every update-event of editable controls, just one line with same code, but ugly! I was thinking of [this](https://codereview.stackexchange.com/questions/68221/overriding-all-access-form-control-afterupdate-methods) – ComputerVersteher Aug 24 '18 at 08:05
  • @ComputerVersteher Yes, that's a better example (less localized) than the one I linked to. I certainly recommend that over single-line event handlers that redirect to a combo box, but that's also because I'm using it in a form with about 300 text/combo boxes. You can also use [this approach](https://codereview.stackexchange.com/q/44361/155474) as an alternative to single-line event handlers that call other functions. I dislike it above classes with event handlers because it's a bit less flexible (can't use event parameters), but it might do here. – Erik A Aug 24 '18 at 08:16
  • At this point, I have about 20 discrete events I want to trap for and I would guess a few more to come. Most of those events happen in one area of the application, albeit on different forms. My concern is not really about the number of events, but that the same fields appear on different forms. I am concerned that I will miss putting the events in all of the places that I need to put them and the reminders will not be created. It makes a huge amount of sense for the event to be tied to the field (or table) itself rather than the form on which it appears. Too bad it is not that simple. – Kim Aug 24 '18 at 17:22
  • I am mulling this over, and will play with the code referenced sometime today to see if it makes sense for me to implement. I will let you know what I come up with. – Kim Aug 24 '18 at 17:43