To show that we are not stuck to CFBuilder, I will use sublime text 2 with ColdBox Platform plugin active. (Available in package control)
Suppose we have a page in our website where visitors can leave their contact details so that a sales representative can contact them with a quote...
This feature or service is divided into three general events:
- Show a form to fill out.
- Handle the form when submitted and add the details to a database.
- Show a page to the visitor telling them the request has been received.
Right click on the handlers folder and create a new file named ContactMe.cfc.
To let ColdBox platform work its magic, type handler followed by the tab key:
There, ColdBox Platform generated a handler ContactMe.cfc with a lot of comments. Because we want this tutorial to be simple, we are not going to use any of the IMPLICIT FUNCTIONS, even though they are extremely useful, so we remove that entire comment block.
At this point we only have one event: function index(event, rc, prc) {}, which we will use to show our contact form. So we need to add two extra events, one for processing the form and one for confirming receipt.
Even though an event has a default view by convention in ColdBox (handlerName/eventName) I still like to set the view explicitly. For me this makes it easier down the line when reviewing and debugging code.
In actual fact, the event we use for processing a form does not have anything to show so it will not need a view and we hint this by explicitly not setting a view in the code.
The process event should actually redirect the browser to the confirmation page so that forms don't get submitted twice.
Lets have a look at our adjusted handler:
/**
* Service to visitors. They can leave their contact details for sales representatives to contact them with a quote
*/
component{
// OPTIONAL HANDLER PROPERTIES
this.prehandler_only = "";
this.prehandler_except = "";
this.posthandler_only = "";
this.posthandler_except = "";
this.aroundHandler_only = "";
this.aroundHandler_except = "";
// REST Allowed HTTP Methods Ex: this.allowedMethods = {delete='POST,DELETE',index='GET'}
this.allowedMethods = {};
function index(event,rc,prc){
event.setView("ContactMe/index");
}
function process(event,rc,prc){
setNextEvent("ContactMe.confirm");
}
function confirm(event,rc,prc){
event.setView("ContactMe/confirm");
}
}
So now we have three events:
- index(event,rc,prc)
- process(event,rc,prc)
- confirm(event,rc,prc)
In two of these functions there is a call to event.setView(ViewFile).
The value of ViewFile represents a path from your views folder to the file you want to display for this event. (.cfm is omitted as ColdBox adds it automatically for you)
So:
- index will show the file at views/ContactMe/index.cfm,
- process will redirect to the confirm event,
- confirm will show the file at views/ContactMe/confirm.cfm
The content can be something static for now, as long as we can see which view is being shown.
views/ContactMe/index.cfm
<h2>Index page</h2>
views/ContactMe/confirm.cfm
<h2>Confirmation page</h2>
So now we can test if our app is working so far.
First reinitialize your application by browsing to:
http://your_server/your_project_folder/index.cfm?fwreinit=true
Then browse to:
- http://your_server/your_project_folder/index.cfm/ContactMe
- http://your_server/your_project_folder/index.cfm/ContactMe/process
- http://your_server/your_project_folder/index.cfm/ContactMe/confirm
index.cfm/ContactMe
index.cfm/ContactMe/process
Does in fact forward to the confirm page.
index.cfm/ContactMe/confirm
Ok not much to look at, but it does confirm that what we did so far works.
The views/ContactMe/index.cfm will have the html code for the form and the views/ContactMe/confirm.cfm will have the html that tells the person who submitted the form that the request was received.
Lets make us a simple contact me form and a simple confirmation page where we thank the visitor.
Fleshing out the views
Lets go ahead and change the views/ContactMe/index.cfm to resemble something like what is shown next:
views/ContactMe/index.cfm
<cfoutput>
<h1>Please Contact Me</h1>
<p>Fill out the form and one of our sales representatives will contact you shortly.</p>
<form action="#event.buildLink('ContactMe.process')#">
<label for="firstName">First Name:</label><br/>
<input id="firstName" name="firstName" type="text" /><br/><br/>
<label for="surname">Surname:</label><br/>
<input id="surname" name="surname" type="text" /><br/><br/>
<label for="phoneNumber">Phone Number:</label><br/>
<input id="phoneNumber" name="phoneNumber" type="text" /><br/><br/>
<label for="emailAddress">Email Address:</label><br/>
<input id="emailAddress" name="emailAddress" type="text" /><br/><br/>
<input id="submit" name="submit" type="submit" value="Submit" />
</form>
</cfoutput>
As said, it is a very simple form...
It introduces a new function though: event.buildLink("ContactMe.process").
This is a very convenient function as we can build our links without having to worry what SES system we are using. ColdBox will build your link according to the SES interceptor that is being used. It is advised to use this function for all your links.
Now lets change the views/ContactMe/confirm.cfm to resemble something like this:
views/ContactMe/confirm.cfm
<cfoutput>
<h1>Request Confirmed</h1>
<p>Thank you! We have received your request and you will be contacted by one of our sales representatives soon.</p>
</cfoutput>
Now visit http://your_server/your_project_folder/index.cfm/ContactMe and you will be presented with a nice form.
We know we actually posted the form to the process event and we were automatically redirected to the confirm event. So now we need to introduce a model to take care of form validation and accessing the database to log the request.
Creating a model
Ok, in my opinion the model integration on ColdBox wiki takes us way too far for this simple example. So I will make this very simple as was the goal of this tutorial.
Create a new file in the model folder called ContactMeService.
Let ColdBox platform work its magic by typing model followed by a tab.
Change the comment to something meaningful.
We give it an optional description.
Note, to keep things simple we will forget about dependency injection for now and instantiate our components the old fashioned way. Also, our ContactMeService will be a does it all component, where in good OO programming the service layer should be separated from the data access layer... We will forget about all that and create a very basic model that does what we need.
For persistence I will use sessions in the old fashioned way as well... And to make it even worse, I am adding the entire service object to the session which is also not advisable. In any case it is just meant to give you a good idea of how things will work.
So here is our simple model after editing it a bit:
model/ContactMeService.cfc
/**
* Handles ContactMe requests
*/
component accessors="true" {
property name="id" type="numeric" default="0";
property name="firstName" type="string" default="";
property name="surname" type="string" default="";
property name="phoneNumber" type="string" default="";
property name="emailAddress" type="string" default="";
ContactMeService function init(){
return this;
}
public boolean function process(rc){
// set the properties with values passed by rc
setFirstName(rc.firstName);
setSurname(rc.surname);
setPhoneNumber(rc.phoneNumber);
setEmailAddress(rc.emailAddress);
// do some minor validation
if(
trim(firstName) == "" or
trim(surname) == "" or
trim(phoneNumber) == "" or
trim(emailAddress) == ""
){
return false;
}
// if all is valid, add the request to the database
var queryService = new Query();
queryService.setDatasource("someDSN");
queryService.setSql("
INSERT INTO ContactMes (
firstName,
surname,
phoneNumber,
emailAddress
) VALUES (
:firstName,
:surname,
:phoneNumber,
:emailAddress
)
OUTPUT INSERTED.id
");
queryService.addParam(name="firstName",value="#firstName#",cfsqltype="VARCHAR");
queryService.addParam(name="surname",value="#surname#",cfsqltype="VARCHAR");
queryService.addParam(name="phoneNumber",value="#phoneNumber#",cfsqltype="VARCHAR");
queryService.addParam(name="emailAddress",value="#emailAddress#",cfsqltype="VARCHAR");
var result = queryService.execute();
// get the output inserted.id and set the id property
setId(result.id);
return true;
}
}
So it is a basic component with accessors set to true so we don't need to write getters and setters.
It has 5 properties and very simplistic, they each represent a column in my ContactMes table.
It has a init() method to initialize returning itself.
It has a process method that:
- Sets the properties by the values passed via rc
- Does some basic validation, returning false when validation fails
- If validation succeeds we add a record to a table in a database and records the new primary id
- Returns true after the insert
Ok we are ignoring any possible errors that could occur when inserting stuff into a database, but I'm sure you get the picture.
Now lets update our handler to make use of this model:
handlers/ContactMe.cfc
/**
* Service to visitors. They can leave their contact details for sales representatives to contact them with a quote
*/
component{
// OPTIONAL HANDLER PROPERTIES
this.prehandler_only = "";
this.prehandler_except = "";
this.posthandler_only = "";
this.posthandler_except = "";
this.aroundHandler_only = "";
this.aroundHandler_except = "";
this.allowedMethods = {};
function index(event,rc,prc){
if(!isDefined("session.contactMeService")){
session.contactMeService = new model.ContactMeService();
}
event.setView("ContactMe/index");
}
function process(event,rc,prc){
event.paramValue("firstName","");
event.paramValue("surname","");
event.paramValue("phoneNumber","");
event.paramValue("emailAddress","");
if(session.contactMeService.process(rc)){
setNextEvent("ContactMe/confirm");
} else {
getPlugin("MessageBox").setMessage("error","All values are required.");
setNextEvent("ContactMe");
}
}
function confirm(event,rc,prc){
structDelete(session, "contactMeService");
event.setView("ContactMe/confirm");
}
}
A few things have changed here:
- In the index method I check if contactMeService is in the session and if not I create a new instance of our model component.
- In the process method I make sure that rc.firstName, ... exists by calling event.paramValue(variable, value). You could compare this to cfparam. Know that rc is a reference to a struct of values in event. So calling methods on event can change the values of rc.
- ColdBox is smart and prepares posted variables by putting them in the rc value. So we can pass rc to the process method of the contactMeService we have in the session.
- If the result is true, we get relocated to the confirm page and delete contactMeService from the session.
- If the result is false, we use a nifty plugin that ColdBox provides to set an error message and relocate back to the form
Now we need some small changes to the view that displays our form to:
- Display error messages.
- Repopulate the form fields.
views/ContactMe/index.cfm
<cfoutput>
<h1>Please Contact Me</h1>
<p>Fill out the form and one of our sales representatives will contact you shortly.</p>
<!--- display any messages in the event --->
#getPlugin("MessageBox").renderit()#
<form action="#event.buildLink("ContactMe.process")#" method="post">
<label for="firstName">First Name:</label><br/>
<input id="firstName" name="firstName" type="text" value="#session.contactMeService.getFirstName()#" /><br/><br/>
<label for="surname">Surname:</label><br/>
<input id="surname" name="surname" type="text" value="#session.contactMeService.getSurname()#" /><br/><br/>
<label for="phoneNumber">Phone Number:</label><br/>
<input id="phoneNumber" name="phoneNumber" type="text" value="#session.contactMeService.getPhoneNumber()#" /><br/><br/>
<label for="emailAddress">Email Address:</label><br/>
<input id="emailAddress" name="emailAddress" type="text" value="#session.contactMeService.getEmailAddress()#" /><br/><br/>
<input id="submit" name="submit" type="submit" value="Submit" />
</form>
</cfoutput>
And there we have it!!! A working example of a handling simple form.






No comments:
Post a Comment