Blog

Dan Card

August 03, 2022

Spread the word


Share your thoughts

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 first installation in this series, we took a tour of the various elements which make up ColdBox.

In the second installation, we looked at creating layouts, views, and routes in the main site.

In the third, we created a module and did the first integration of our existing code into our ColdBox 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.

Recording: https://cfcasts.com/series/ortus-webinars-2022/videos/getting-started-with-the-legacy-migration-with-dan-card

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!

Recording: https://cfcasts.com/series/ortus-webinars-2022/videos/legacy-migration-follow-up:-using-coldbox-with-an-existing-code-base

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.

https://intothebox.org/#workshops-2022

Add Your Comment

Recent Entries

Why BoxLang When You Have Kotlin, Groovy, Scala, and more…

Why BoxLang When You Have Kotlin, Groovy, Scala, and more…

As we approach a stable release of BoxLang and our continued marketing reaches more folks, many have asked about its purpose. Why create a new language when the JVM ecosystem already includes established languages like Kotlin, Groovy, and Scala, to name a few.

Luis Majano
Luis Majano
December 18, 2024
ColdBox Free Tip 6 - Using Routing with Wildcard Domains!

ColdBox Free Tip 6 - Using Routing with Wildcard Domains!

ColdBox gives you the flexibility to create domain-specific routes, making it perfect for multi-tenant applications or projects that need to respond differently based on the domain or subdomain being accessed. In this tip, we’ll dive into how to use the withDomain() method to create routes that match specific domains or sub-domains.

Maria Jose Herrera
Maria Jose Herrera
December 18, 2024