So far we've covered defining events and piecing together the user interface through the controller (mach-ii.xml). Now we need to move queries and business logic out of the View and into the Model. To get started, we'll move queries that return more than one record into Gateway objects using Coldfusion Components.
Preparation
We'll begin with the Version 2 files.
If you didn't switch to the version 2 files at the end of part 3:
- Rename the version 1 mach-ii.xml file to mach-ii.01.xml
- Rename mach-ii.02.xml to mach-ii.xml
Mach-II should be loading version 2 of mach-ii.xml using the "/02/" folders.
As we step through this process, we'll begin seeing version 3 files.
Terms to know:
- A function is also known as a method.
- A Coldfusion Component (CFC) will often be referred to as an Object.
Getting organized
Open up version 2 of the contact list. It's pretty simple:
- HTML
- <cfquery>
- <cfoutput> looping over more HTML to display the query records
2<p><a href="contact_form.cfm?CONTACT_ID=0">Add a Contact</a></p>
3
4<cfquery name="qContacts" datasource="#request.DSN#">
5 SELECT
6 CONTACT_ID,
7 CONTACT_FIRST_NAME,
8 CONTACT_LAST_NAME
9 FROM
10 CF_CONTACTS
11 ORDER BY
12 CONTACT_LAST_NAME, CONTACT_FIRST_NAME
13</cfquery>
14<ul>
15 <cfoutput query="qContacts">
16 <li>
17 [ <a href="contact_form.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">Edit</a> ]
18 <a href="contact_detail.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">#qContacts.CONTACT_LAST_NAME#, #qContacts.CONTACT_FIRST_NAME#</a>
19 </li>
20 </cfoutput>
21</ul>
Let's first do something simple and organize the code so that all queries (and potentially any business logic) is run at the top of the page.
2 SELECT
3 CONTACT_ID,
4 CONTACT_FIRST_NAME,
5 CONTACT_LAST_NAME
6 FROM
7 CF_CONTACTS
8 ORDER BY
9 CONTACT_LAST_NAME, CONTACT_FIRST_NAME
10</cfquery>
11
12<p><a href="contact_form.cfm?CONTACT_ID=0">Add a Contact</a></p>
13<ul>
14 <cfoutput query="qContacts">
15 <li>
16 [ <a href="contact_form.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">Edit</a> ]
17 <a href="contact_detail.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">#qContacts.CONTACT_LAST_NAME#, #qContacts.CONTACT_FIRST_NAME#</a>
18 </li>
19 </cfoutput>
20</ul>
contacts.cfm is part of the presentation layer, so we need to move the query over to the Model. Since this query returns a list of records it should be moved into what's called a Gateway object.
CFFunction Overview
If you've not used <cffunction> before, lets go over its attributes.
- name: The name of the function. No two functions in a CFC can have the same name.
- access:
- A public function can be called from inside or outside the CFC.
- private functions can only be called by other functions in the same file.
- package access allows calls from functions in the same file and functions from other CFC files in the same folder (package).
- remote functions are used as Web Services, but that's a discussion for another primer
- output: since we're only working with data and logic, always set this to false
- returntype: the datatype returned from the function - string, numeric, struct, query, component name, etc.
Now take a look at getAllContacts(). Notice we've set a variable that's the same name as the <cfquery>
2<cfquery name="qContacts" datasource="#variables.DSN#">
This creates the variable "qContacts" in the var scope, which makes it a function local variable. Now "qContacts" is only available to the function getAllContacts() and cannot be read or have its value changed by another function. This is important when you have more than one function using variables with the same name.
Always var scope names of queries, cfloop indexes and other tag-defined variables
varScoper is a tool designed by Mike Schierberl "to identify variables created within a cffunction that don't have a corresponding (cfset var) statement."
Coldfusion Components
Here is ContactGateway.cfc which contains two functions.
2<cfcomponent name="ContactGateway" output="false" hint="Defines Gateway functions for Contacts">
3
4 <cffunction name="init" access="public" output="false" returntype="ContactGateway" hint="constructor">
5 <cfargument name="DSN" type="string" required="true" hint="datasource" />
6 <cfset variables.DSN = arguments.DSN />
7 <cfreturn this />
8 </cffunction>
9
10 <cffunction name="getAllContacts" access="public" output="false" returntype="query" hint="returns a query recordset of contacts">
11 <cfset var qContacts = "" />
12
13 <cfquery name="qContacts" datasource="#variables.DSN#">
14 SELECT
15 CONTACT_ID,
16 CONTACT_FIRST_NAME,
17 CONTACT_LAST_NAME
18 FROM
19 CF_CONTACTS
20 ORDER BY
21 CONTACT_LAST_NAME, CONTACT_FIRST_NAME
22 </cfquery>
23
24 <cfreturn qContacts />
25
26 </cffunction>
27
28</cfcomponent>
init()
This is the constructor method. This method will initialize the component, set data into the variables scope of the component and return an instance of the component.
The returntype attribute should specify the CFC file name with or without the dot separated path to the component.
In order to pass existing variables into a component, you can inject those values into the object using the variables scope.
2<cfset variables.DSN = arguments.DSN />
Finally, return the object.
getAllContacts()
This runs the query "qContacts" and returns the recordset. There are three things to note about this function:
- uses the returntype "query"
- the name of the query is var scoped
- the cfquery uses the datasource "#variables.DSN#", which was defined by the init() method
Object Instantiation
To create an instance of the Gateway object:
You want to avoid calling shared scoped variables (i.e. application.*, session.*) from inside a CFC, so make sure to inject them into the component through the init() method.
If the value of session.DSN is changed outside of the CFC, the value "inside" the CFC (in the variables scope) is unaffected.
However, if the session variable is a non-simple datatype like a struct or another component (i.e. a Bean), changing the value inside the CFC will change the value of the actual session variable outside the CFC. See There are no Pointers in ColdFusion for more information.
Calling the Model from the View
Let's go back to contacts.cfm and get the contact records from the Gateway object
2
3<p><a href="contact_form.cfm?CONTACT_ID=0">Add a Contact</a></p>
4<ul>
5 <cfoutput query="qContacts">
6 <li>
7 [ <a href="contact_form.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">Edit</a> ]
8 <a href="contact_detail.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">#qContacts.CONTACT_LAST_NAME#, #qContacts.CONTACT_FIRST_NAME#</a>
9 </li>
10 </cfoutput>
11</ul>
You can see all the Gateway objects at this point under the version 3 folders.
Next step
We've started moving queries out of the View and into the Model, but now we're creating objects in the View. Anytime the object definition changes, we may need to update all the View files that reference it and that's what we were trying to avoid with the queries.
Using Mach-II Listeners we'll remove object instantiation from the View and reference objects and methods from the Controller (mach-ii.xml).