Free Goodies: Send Email From Components

I often need to send an email when a certain action is taken. The action is usually taken in a CFC. I have often seen email sent out when the CFC method is called, but this means you must remember to send the email each time you call the method.

Better, I think, to send the email from the method itself. Unfortunately, this means that you must pass into the component (preferably on initialization) all of the information you need to send an email message (generally, the mail server and the from address but potentially a username and password as well).

This can add several aguments to the initialization method of the component that aren't central to the task that the component performs.

To avoid this, I created a Mailer component that I initialize with this information. I then pass the Mailer component to my action component and use it to send email.

For example:

I will instantiate Mailer with my mail server and default from address (I can choose a different from address for any given message):

<cfset Mailer = CreateObject("component","com.sebtools.Mailer").init("mail.example.com","robot@example.com")>

If I needed a username and password, I would specify that during initialization as well. Note that Mailer must be installed in the com/sebtools directory either under the web root or in the custom tags directory.

I am assuming that I already have a Data Manager component loaded in the variable "DataMgr" (just to help keep the example short).

Now, I initialize my UserMgr component:

<cfset UserMgr = CreateObject("component,"UserMgr").init(DataMgr,Mailer)>

Now, in order to update a user and send an email to that user to let them know their record has been updated, I want to be able to call that method like this (on a form action page):

<cfset UserMgr.saveUser(argumentCollection=Form)>

Here is the UserMgr component:

UserMgr

Breaking it down by method:

<cffunction name="init" access="public" returntype="any" output="no">
    <cfargument name="DataMgr" type="any" required="yes">
    <cfargument name="Mailer" type="any" required="yes">
   
    <cfset variables.DataMgr = arguments.DataMgr>
    <cfset variables.Mailer = arguments.Mailer>
   
    <cfreturn this>
</cffunction>


This is the init() method inializes and returns the component. It takes the DataMgr and Mailer as arguments and makes them available (via variables scope) to the rest of the component.

<cffunction name="getUser" access="public" returntype="query" output="no">
    <cfargument name="UserID" type="numeric" required="yes">
   
    <cfreturn variables.DataMgr.getRecord("users",arguments)>
</cffunction>


The getUser() method returns a recordset of the given user. It is just included for completeness.

<cffunction name="saveUser" access="public" returntype="numeric" output="no">
    <cfargument name="UserID" type="string" required="no">
    <cfargument name="FirstName" type="string" required="no">
    <cfargument name="LastName" type="string" required="no">
    <cfargument name="Email" type="string" required="no">
   
    <cfset var result = variables.DataMgr.saveRecord("users",arguments)>
    <cfset var qUser = getUser(result)>
   
    <cfif Len(qUser.Email)>
        <cfset variables.Mailer.send(qUser.Email,"Record Updated","Just to let you know, your record was updated.")>
    </cfif>
   
    <cfreturn result>
</cffunction>


The saveUser() method saves the record and sends an email to the user. Any action could have been taken before or after calling the Mailer.send() method - this was just one example. Note that DataMgr was here only to keep the examples brief - it is not needed to use Mailer.

That's it!

Mailer can also handle dynamic data and multi-part email messages, but those are topics for another day (though a glance at the documentation should provide the answers).

Mailer is a free download and has documentation available.

Platform Independence with Java

One of the great things about ColdFusion is that it works the same on any supported platform. Still, some things are always different. Carriage returns and directory separators are different based on your OS. Fortunately, you can use Java to bridge the gap.

Directory Path Separators

Windows uses "\" to separate directories but other operating systems use "/". Java, however, will tell you which one is used by your system.

<cfset fileObj = createObject("java", "java.io.File")>
<cfset dirdelim = fileObj.separator>


Now the "dirdelim" variable will hold your directory delimeter ("\" for windows and "/"for other systems).

Carriage Returns

Carriage returns (end of line characters) also vary based on the operating system (Mac uses character #13, Linux uses #10, Windows uses #13 followed by #10).

Until recently I handled this like so:
<cfset cr = "
">


This has always worked, but has always felt a bit ugly. It also sometimes threw off the color-coding of my editor.

After I uploaded XmlHumanReadable, I got an email message from Fabio Serra suggesting a better technique.

<cfset
cr = CreateObject("java","java.lang.System").getProperty("line.separator")>

Much like the directory path separators, this returns the carriage return character for your operating system.

Summary

If you take advantage of these Java commands, your code can be easily ported between different platforms without worry.

I am sure there are other simple Java commands that can make our lives easier. If you know any, I would love to hear about them.

Save Table Relationship Data

DataMgr offers an easy way to save data describing many-to-many relationships. This normally tedious process is made easy with this free component.

Normally when you save relationship data, you have to add new records in a relationship table and remove records in the same table for relationships that no longer exist. In many cases, you don't want to delete and re-add records because of extra data that might also be stored in that table. Often this can involve writing tedious code with a lot of looping.

Fortunately, DataMgr can take care of all of that for you.

Let's see an example.

Suppose I have an "Admins" table (holding administrators for our site), a "Permissions" table (holding possible permissions for administrators) and an "Admins2Permissions" table (indicating which administrators have which permissions).

Let's say that "Admins2Permissions" has two fields "AdminID" and "PermissionID" referencing the primary key fields of the Admins table and the Permissions table, respectively.

Let's also say that we have a variable called "permissionslist", which holds a list of permissions for an admin (specifically, it holds a list of the primary key values from the Permissions table). This could be passed in from a checkbox field, for example. The primary key value for the admin is stored in a variable called "admin".

To save the relationship, we would use this code (assuming that you have the DataMgr component stored in variables.DataMgr):
<cfset variables.DataMgr.saveRelationList("Admins2Permissions","AdminID",admin,"PermissionID",permissionslist)>

Using that code, DataMgr will remove any records for that admin that are not included in the list and add any records that are in the list and not in the Admins2Permissions table for that admin.

The saveRelationList method takes five (5) arguments:

  1. tablename: The name of the table holding the relationships.
  2. keyfield: The name of the field holding the key value for the relationships.
  3. keyvalue: The value of out primary key.
  4. multifield: The field holding the "many" side of this relationship.
  5. multilist: The list of values to be stored for the multifield as associated with the keyvalue in this table.
In order to instantiate DataMgr for use as in the example above, you need only initialize it with the datasouce. For example, if you wanted to initialize it to use a datasource of "MyDSN" (pointing to a MS SQL database), you would do this:
<cfset variables.DataMgr = CreateObject("component","com.sebtools.DataMgr_MSSQL").init("MyDSN")>

For a MySQL database, you would use this:
<cfset variables.DataMgr = CreateObject("component","com.sebtools.DataMgr_MySQL").init("MyDSN")>

You can also use Access (DataMgr_Access) or PostGreSQL (DataMgr_PostGreSQL). It should be easy to add support for other databases as well.

You can download DataMgr for free.


Version Note:
As of today (March 7, 2006), DataMgr is version 1.0.1. Unfortunately, support for MS Access prior to ColdFusion MX 7 still hasn't been fixed. I have, however, adjusted the error message to accurately reflect that. All errors thrown by DataMgr are also now of type "DataMgr". Additionally, a few small bug fixes have been made.

BlogCFC was created by Raymond Camden. This blog is running version 5.8.001.