Recently, I did a webinar on Refactoring Legacy Code and the question came up about whether or not it was possible to use ColdBox with existing code without converting everything to a ColdBox module or making changes to the existing codebase.
In the second installation, we looked at creating layouts, views, and routes in the main site.
See the previous posts about setting up the sample code from Forgebox at https://www.forgebox.io/view/coldbox-existing-code-blog.
Method 2: Using ColdBox as a new "view"
Many times (not always, but usually) a site / app gets a facelift as part of a revamp but the accumulated logic / server side code might not change too drastically. Let's say that as part of a new initiative we want to start moving toward using a css framework rather than developing all of our css in-house. To keep things simple, we're going to use the latest Bootstrap which (as of this writing) is 5.2.0. Also to keep this simple, we are going to just do one step
up from the include example and have all of our logic in our .cfm page. We'll worry about separation of concerns later.
Note: Check out the file /coldboxsite/modules_app/myco/views/home/byData.cfm
and the URL http://myco.local/myco/accountsByData to see the completed page.
There are three steps to this process:
Step 1: Create the Route
In /modules_app/myco/config/Router.cfc
, create the route accountsByData
which runs the home.byData
event. In the previous posts we saw that we can create this by simply adding route("/accountsByData","home.byData")
to the configure()
method.
Step 2: Create the View
In the /modules_app/myco/views/home/
folder, create the file byData.cfm
. Because this is a view, and not a layout, we don't include the html, head, and body tags. We would probably also put the links and script tags to the bootstrap libraries in our layout if this were a "real" project, but for our purposes, we'll put them here.
The key part of this example is this:
<cfset people = createObject("com.mycode.people.Accounts").allClients() />
<cfset balances = createObject("com.mycode.accounting.accounting").allAccounts() />
When we set up our cfmappings, we mapped /com
to the com folder in our "existing code" (/mycode
in the sample code). Once we did that, we can access those components the same way we would any other code. From there it is just a straightforward project to put in our HTML and then create our rows.
<cfset people.each(function(item, index){
writeOutput("
<tr>
<td>#index#</td>
<td>#item.id#</td>
<td>#item.first#</td>
<td>#item.last#</td>
<td>#balances.keyExists(item.id) ? balances[item.id] : ''#</td>");
}) />
In case this looks odd to anyone, we're using the member function people.each()
. Member functions are functionality that are built directly into the data structure, in this case, an array. (The data structure itself is obviously a class which is why it can have methods). We also could have used the more verbose ArrayEach(people, function() )
. Either way, inside the function, we can use script syntax which is why we have writeOutput("")
surrounding our HTML. Whichever format you use, each()
will loop over the array and pass each element into the function submitted. We are using each because we aren't expecting anything back from our loop. If we were expecting a return, we could use map()
but there is no need in this case.
We also used the ternary operator in the <td>
. Instead of typing
If( balances.keyExists( item.id ) ){
WriteOutput( "#balances[ item.id ]#" )
} else {
writeOutput( '' );
}
We can write #balances.keyExists( item.id ) ? balances[ item.id ] : ''
. The first section is what is evaluated. The second section (after the ?
) is what happens if the first part is true and the third (after the :
) is what happens if the first part if false.
Open this example at http://myco.local/myco/accountsByData and compare it to the original at http://mycooriginal.local/ and you can see the difference.
Method 3: Using a Handler
In method 2, we had our logic (all the createObject()
code) and the view (our HTML table) on the same.cfm
page. We want to actually separate them to have a better separation of concerns, in this case, Horizontal Separation of Concerns. A handler is a method in a cfc
that is run in response to an event such as a route being accessed.
Step 1: Create the route
In the /config/Router.cfc
of our myco
module, add route( "/accountsWithHandler", "myCo.accountBalances" );
Step 2: Create the Handler
Since a handler is, at its core, a cfc
, all we need to do is create a .cfc
. However, we can also use CommandBox to create our handler for us. Navigate to /modules_app/myco
and then type coldbox create handler name=myco
. For our purposes, we're simply going to create a cfc
.
component {
}
In our component, we're going to add this function:
public function accountBalances( event, rc, prc ){
event.setPrivateValue( "people", createObject( "com.mycode.people.Accounts" ).allClients() );
event.setPrivateValue("balances",createObject("com.mycode.accounting.accounting").allAccounts() );
event.setView( "home/withHandler" );
}
To unpack this, we need to look at the arguments which are being sent in
event
– On every request to a ColdBox event, the framework creates an object that models the incoming request. This object is called the Request Context Object.
rc
– The Request Collection which contains the FORM/REMOTE/URL data merged into a single structure. This is considered to be unsafe data as it comes from any request.
prc
– The Private Request Collection which is a structure that can be used to safely store sensitive data. This structure cannot be modified from the outside world.
In this method, we are still using our createObject()
code but we are putting the results into the event component as a privateValue
. This is available to our code, including the view, for the rest of the request.
As a final act, we are setting the view to be home/withHandler
. Since this is in our module, ColdBox will look in our module's view folder for this .cfm
file.
Step 3: Create the view
Duplicate the byData.cfm
file in the sample code and call it withHandler.cfm
. Remove the two createObject()
lines and change people.each()
to prc.people.each()
.
This is much better separation of concerns.
Method 4: Passing in Variables
This isn't so much of a new method but a consideration that is going to be important. We pass in variables from the browser all the time via forms, links, and so on. How does ColdBox handle that?
Step 1:
Duplicate the Handler method accountBalances
and call it passingVariables
Step 2:
Create the route route( "/accountsWithVariables", "myCo.passingVariables" );
Step 3:
Duplicate /views/home/withHandler.cfm
and call it passingVariables.cfm
in the same folder.
In the passingVariables.cfm
file, change the ID to a link using either
href="/myco/passingVariables?activeReview=2
or
event.buildLink("myco.accountsWithVariables","activeReview=2")
Option A will send the user to http://myco.local/myco/accountsWithVariables?activeReview=2
Option B will send the user to http://myco.local/myco/accountsWithVariables/activeReview/2.
In both of these formats, the variables activeReview
will be in the event.rc.activeReview
with the value of 2
.
In our table, add some simple logic such as:
var emphasis = event.getValue("activeReview","") == item.id ? "font-weight:1000;" : "";
and then adapt the table row to be <tr style="#emphasis#">
The row which was chosen will appear emboldened. Perhaps not the most useful functionality but it illustrates our purposes.
Conclusion
We've now looked at three ways we can start using ColdBox as a new view to our existing data while keeping the data in place in old file structure so it is accessible by our existing site and we are not stuck maintaining two code bases. These methods include:
- A simple include referencing our old
.cfm
files, - Creating a new layout and view and using
createObject()
in our view to obtain data from our existing code, and - Adding a handler which accesses the data and adds it to the event object which is then available to the view.
In the next (and final?) post in this series, we'll look at few ways we can use a tool which is integrated into ColdBox called WireBox to reference our data. This will allow us to remove the createObject()
calls completely from our handlers and get rid of more code clutter.
Did you miss the June Webinar - Getting started with the Legacy Migration with Dan Card
We will look at the process of converting legacy .cfm based sites into a more modern coding design which has less overall code, is easier to maintain and manage, mistakes and errors can more readily and speedily identified and fixed, and is easier to read.
Did you miss: Legacy Migration Follow Up: Using Coldbox with an Existing Code Base with Dan Card
July 29th 2022: Time 11:00 AM Central Time ( US and Canada )
Dan Card will present a follow-up to his June webinar: Getting started with the Legacy Migration. Dan received some good questions, so July's Webinar: Legacy Migration Follow Up: Using Coldbox with an Existing Code Base with Dan Card. If you have a more traditional/legacy codebase and want to modernize with ColdBox but don't know where to start, this webinar is just for you!
Find out more about Dan Card's workshop at Into the Box - Legacy Code Conversion to the Modern World
This one-day workshop will focus on converting legacy .cfm based sites into a more modern coding design that has less overall code, is easier to maintain and manage, mistakes and errors can be more readily and speedily identified and fixed, and is easier to read.
Add Your Comment