Install CFWheels and get a local development server running
By far the quickest way to get started with CFWheels is via CommandBox. CommandBox brings a whole host of command line capabilities to the CFML developer. It allows you to write scripts that can be executed at the command line written entirely in CFML. It allows you to start a CFML server from any directory on your machine and wire up the code in that directory as the web root of the server. What's more is, those servers can be either Lucee servers or Adobe ColdFusion servers. You can even specify what version of each server to launch. Lastly, CommandBox is a package manager for CFML. That means you can take some CFML code and package it up into a module, host it on ForgeBox.io, and make it available to other CFML developers. In fact we make extensive use of these capabilities to distribute CFWheels plugins and templates. More on that later.
One module that we have created is a module that extends CommandBox itself with commands and features specific to the CFWheels framework. The CFWheels CLI module for CommandBox is modeled after the Ruby on Rails CLI module and gives similar capabilities to the CFWheels developer.
The first step is to get CommandBox downloaded and running. CommandBox is available for Windows, Mac & Linux, and can be installed manually or using one of the respective package managers for each OS. You can use Chocolatey on Windows, Homebrew on MacOS, or Yum/Apt on Linux depending on your flavor of Linux. Please follow the instructions on how to install CommandBox on your particular operating system. At the end of the installation process you want to make sure the box
command is part of your system path so you can call the command from any directory on your system.
Once installed, you can either double-click on the box
executable which opens the CommandBox shell window, or run box
from a CMD window in Windows, Terminal window in MacOS, or shell prompt on a Linux server. Sometimes you only want to call a single CommandBox command and don't need to launch a whole CommandBox shell window to do that, for these instances you can call the CommandBox command directly from your default system terminal window by prefixing the command with the box
prefix.
So to run the CommandBox version
command you could run box version from the shell or you could launch the CommandBox shell and run version inside it.
box version
version
This is a good concept to grasp, cause depending on your workflow, you may find it easier to do one versus the other. Most of the commands you will see in these CLI guides will assume that you are entering the command in the actual CommandBox shell so the box
prefix is left off.
Okay, now that we have CommandBox installed, let's add the CFWheels CLI module.
install cfwheels-cli
Installing this module will add a number of commands to your default CommandBox installation. All of these commands are prefixed by the wheels
name space. There are commands to create a brand new CFWheels application or scaffold out sections of your application. We'll see some of these commands in action momentarily.
Now that we have CommandBox installed and extended it with the CFWheels CLI module, let's start our first CFWheels app from the command line. We'll look at the simplest method for creating a CFWheels app and starting our development server.
wheels generate app myApp server start
A few minutes after submitting the above commands a new browser window should open up and display the default CFWheels congratulations screen.
So what just happened? Since we only passed the application name myApp
to the wheels generate app
command, it used default values for most of its parameters and downloaded our Base template (cfwheels-base-template) from ForgeBox.io, then downloaded the framework core files (cfwheels) from ForgeBox.io and placed it in the wheels directory, then configured the application name and reload password, and started a Lucee server on a random port.
A Word About Command Aliases
CommandBox commands have the capability to be called by multiple names or aliases. The command above wheels generate app
can also be initiated by typing wheels g app
. In fact g
is an alias for generate
so wherever you see a command in the CLI documentation that has generate
in it you can substitute g
instead.
In addition to shortening generate
to g
, aliases can completely change the name space as well. A command that you haven't seen yet is the wheels generate app-wizard
command. This command guides the user through a series of menu options, building up all the parameters needed to customize the start of a new CFWheels project. You're likely to use the wizard when starting a new CFWheels application so it's good to become familiar with it.
This command has the normal alias referenced above at wheels g app-wizard
but it also has an additional alias at wheels new
which is the command more prevalent in the Rails community. So the three commands wheels generate app-wizard
, wheels g app-wizard
, and wheels new
all call the same functionality which guides the user though a set of menus, collecting details on how to configure the desired app. Once all the parameters have been gathered, this command actually calls the wheels generate app
command to create the actual CFWheels application.
This getting started guide has taken you from the very beginning and gotten you to the point where you can go into any empty directory on your local development machine and start a CFWheels project by issuing a couple of CLI commands. In later guides we'll explore these options further and see what else the CLI can do for us.
A quick tutorial that demonstrates how quickly you can get database connectivity up and running with CFWheels.
CFWheels's built in model provides your application with some simple and powerful functionality for interacting with databases. To get started, you will make some simple configurations, call some functions within your controllers, and that's it. Best yet, you will rarely ever need to write SQL code to get those redundant CRUD tasks out of the way.
We'll learn by building part of a sample user management application. This tutorial will teach you the basics of setting up a resource that interacts with the CFWheels ORM.
Download source code
You can download all the source code for this sample application from https://github.com/dhgassoc/Cfwheels-Beginner-Tutorial-Hello-Database
By default, CFWheels will connect to a data source wheels.fw
. To change this default behavior, open the file at app/config/settings.cfm
. In a fresh install of CFWheels, you'll see the follwing code:
These lines provide CFWheels with the necessary information about the data source, URL rewriting, and reload password for your application, and include the appropriate values. This may include values for dataSourceName
, dataSourceUserName
, and dataSourcePassword
. More on URL rewriting and reload password later.
CFWheels supports MySQL, SQL Server, PostgreSQL, and H2. It doesn't matter which DBMS you use for this tutorial; we will all be writing the same CFML code to interact with the database. CFWheels does everything behind the scenes that needs to be done to work with each DBMS.
That said, here's a quick look at a table that you'll need in your database, named users
:
Note a couple things about this users
table:
The table name is plural.
The table has an auto-incrementing primary key named id
.
These are database conventions used by CFWheels. This framework strongly encourages that everyone follow convention over configuration. That way everyone is doing things mostly the same way, leading to less maintenance and training headaches down the road.
Fortunately, there are ways of going outside of these conventions when you really need it. But let's learn the conventional way first. Sometimes you need to learn the rules before you can know how to break them.
Next, open the file at app/config/routes.cfm
. You will see contents similar to this:
We are going to create a section of our application for listing, creating, updating, and deleting user records. In CFWheels routing, this requires a plural resource, which we'll name users
.
Because a users
resource is more specific than the "generic" routes provided by CFWheels, we'll list it first in the chain of mapper method calls:
This will create URL endpoints for creating, reading, updating, and deleting user records:
Name is referenced in your code to tell CFWheels where to point forms and links.
Method is the HTTP verb that CFWheels listens for to match up the request.
URL Path is the URL that CFWheels listens for to match up the request.
Don't forget to reload
You will need to reload your application after adding new routes!
First, let's create a simple form for adding a new user to the users
table. To do this, we will use CFWheels's form helper functions. CFWheels includes a whole range of functions that simplifies all of the tasks that you need to display forms and communicate errors to the user.
Now create a new file in app/views/users
called new.cfm
. This will contain the view code for our simple form.
Next, add these lines of code to the new file:
What we've done here is use form helpers to generate all of the form fields necessary for creating a new user in our database. It may feel a little strange using functions to generate form elements, but it will soon become clear why we're doing this. Trust us on this one… you'll love it!
To generate the form tag's action
attribute, the startFormTag() function takes parameters similar to the linkTo()function that we introduced in the Beginner Tutorial: Hello World tutorial. We can pass in controller, action, key
, and other route- and parameter-defined URLs just like we do with linkTo().
To end the form, we use the endFormTag() function. Easy enough.
The textField() and passwordField() helpers are similar. As you probably guessed, they create <input>
elements with type="text"
and type="password"
, respectively. And the submitTag() function creates an <input type="submit" />
element.
One thing you'll notice is the textField() and passwordField() functions accept arguments called objectName
and property
. As it turns out, this particular view code will throw an error because these functions are expecting an object named user
. Let's fix that.
All of the form helper calls in our view specify an objectName
argument with a reference to a variable named user
. That means that we need to supply our view code with an object called user
. Because the controller is responsible for providing the views with data, we'll set it there.
Create a new ColdFusion component at app/controllers/Users.cfc
.
As it turns out, our controller needs to provide the view with a blank user
object (whose instance variable will also be called user
in this case). In our new action, we will use the model() function to generate a new instance of the user model.
To get a blank set of properties in the model, we'll also call the generated model's new() method.
Notice how we have prefixed the model() function with application.wo
. This is because, in CFWheels, all the global functions and functions for your controller reside in the application.wo
structure. So, whenever you need to call a global function or a function for your controller, you have to prefix them with application.wo
. You will see its use throughout the documentation wherever required.
CFWheels will automatically know that we're talking about the users
database table when we instantiate a user
model. The convention: database tables are plural and their corresponding CFWheels models are singular.
Why is our model name singular instead of plural? When we're talking about a single record in the users
database, we represent that with an individual model object. So the users
table contains many user
objects. It just works better in conversation.
Now when we run the URL at http://localhost/users/new
, we'll see the form with the fields that we defined.
The HTML generated by your application will look something like this:
So far we have a fairly well-formed, accessible form, without writing a bunch of repetitive markup.
Next, we'll code the create
action in the controller to handle the form submission and save the new user to the database.
A basic way of doing this is using the model object's create() method:
Because we used the objectName
argument in the fields of our form, we can access the user data as a struct in the params
struct.
There are more things that we can do in the create
action to handle validation, but let's keep it simple in this tutorial.
Notice that our create
action above redirects the user to the users
index route using the redirectTo() function. We'll use this action to list all users in the system with "Edit" links. We'll also provide a link to the "New User" form that we just coded.
First, let's get the data that the listing needs. Create an action named index
in the users
controller like so:
This call to the model's findAll() method will return a query object of all users in the system. By using the method's order
argument, we're also telling the database to order the records by username
.
In the view at app/views/users/index.cfm
, it's as simple as looping through the query and outputting the data
When to use EncodeForHtml
You'll see references to EncodeForHtml
in some of our examples that output data. This helps escape HTML code in data that attackers could use to embed inject harmful JavaScript. (This is commonly referred to as an "XSS attack," short for "Cross-site Scripting attack.")
A rule of thumb: you do not need to use EncodeForHtml
when passing values into CFWheels helpers like linkTo
, buttonTo
, startFormTag
, textField
, etc. However, you need to escape data that is displayed directly onto the page without a CFWheels helper.
We'll now show another cool aspect of form helpers by creating a screen for editing users.
You probably noticed in the code listed above that we'll have an action for editing a single users
record. We used the linkTo() form helper function to add an "Edit" button to the form. This action expects a key
as well.
Because in the linkTo() form helper function we specified the parameter key
, Wheels adds this parameter into the URL when generating the route.
Wheels will automatically add the provided 'key' from the URL to the params struct in the controllers edit() function.
Given the provided key
, we'll have the action load the appropriate user
object to pass on to the view:
The view at app/views/users/edit.cfm
looks almost exactly the same as the view for creating a user:
But an interesting thing happens. Because the form fields are bound to the user
object via the form helpers' objectName
arguments, the form will automatically provide default values based on the object's properties.
With the user
model populated, we'll end up seeing code similar to this:
Pretty cool, huh?
There's a lot of repetition in the new
and edit
forms. You'd imagine that we could factor out most of this code into a single view file. To keep this tutorial from becoming a book, we'll just continue on knowing that this could be better.
Now we'll create the update
action. This will be similar to the create
action, except it will be updating the user object:
To update the user
, simply call its update() method with the user
struct passed from the form via params
. It's that simple.
After the update, we'll add a success message using the Flash and send the end user back to the edit form in case they want to make more changes.
Notice in our listing above that we have a delete
action. Here's what it would look like:
We simply load the user using the model's findByKey() method and then call the object's delete() method. That's all there is to it.
We've shown you quite a few of the basics in getting a simple user database up and running. We hope that this has whet your appetite to see some of the power packed into the CFWheels framework. There's plenty more.
Be sure to read on to some of the related chapters listed below to learn more about working with CFWheels's ORM.
Column Name | Data Type | Extra |
---|---|---|
Name | Method | URL Path | Description |
---|---|---|---|
id
int
auto increment
username
varchar(100)
varchar(255)
passwd
varchar(15)
users
GET
/users
Lists users
newUsers
GET
/users/new
Display a form for creating a user record
users
POST
/users
Form posts a new user record to be created
editUser
GET
/users/[id]/edit
Displays a form for editing a user record
user
PATCH
/users/[id]
Form posts an existing user record to be updated
user
DELETE
/users/[id]
Deletes a user record
Using CFWheels to develop web applications with AJAX features is a breeze. You have several options and tools at your disposal, which we'll cover in this chapter.
CFWheels was designed to be as lightweight as possible, so this keeps your options fairly open for developing AJAX features into your application.
While there are several flavors of JavaScript libraries out there with AJAX support, we will be using the jQuery framework in this tutorial. Let's assume that you are fairly familiar with the basics of jQuery and know how to set it up.
For this tutorial, let's create the simplest example of all: a link that will render a message back to the user without refreshing the page.
In this example, we'll wire up some simple JavaScript code that calls a CFWheels action asynchronously. All of this will be done with basic jQuery code and built-in CFWheels functionality.
First, let's make sure we've got an appropriate route setup. It might be you're still using the default wildcard()
route which will create some default GET
routes for the controller/action
pattern, but we'll add a new route here just for practice. We are going to create a route named sayHello
and direct it to the hello
action of the say
controller. There are two ways you could write this code a long hand method specifying the controller and action separately as well as a short hand method that combines the two into a single parameter.
The longhand way would look like:
The shorthand method would look like:
You can decide which method you prefer. Both sets of code above are equivalent.
Then, let's create a link to a controller's action in a view file, like so:
That piece of code by itself will work just like you expect it to. When you click the link, you will load the hello
action inside the say
controller.
But let's make it into an asynchronous request. Add this JavaScript (either on the page inside script
tags or in a separate .js
file included via javaScriptIncludeTag() ):
With that code, we are listening to the click
event of the hyperlink, which will make an asynchronous request to the hello
action in the say
controller. Additionally, the JavaScript call is passing a URL parameter called format
set to json
.
Note that the success
block inserts keys from the response into the empty h1
and p
blocks in the calling view. (You may have been wondering about those when you saw the first example. Mystery solved.)
The last thing that we need to do is implement the say/hello
action. Note that the request expects a dataType
of JSON
. By default, CFWheels controllers only generate HTML responses, but there is an easy way to generate JSON instead using CFWheels's provides() and renderWith() functions:
In this controller's config()
method, we use the provides() function to indicate that we want all actions in the controller to be able to respond with the data in HTML or JSON formats. Note that the client calling the action can request the type by passing a URL parameter named format or by sending the format
in the request header.
The call to renderWith() in the hello
action takes care of the translation to the requested format. Our JavaScript is requesting JSON, so Wheels will format the greeting
struct as JSON automatically and send it back to the client. If the client requested HTML or the default of none, Wheels will process and serve the view template at app/views/say/hello.cfm
. For more information about provides() and renderWith(), reference the chapter on Responding with Multiple Formats.
Lastly, notice the lines where we're setting greeting["message"]
and greeting["time"]
. Due to the case-insensitive nature of ColdFusion, we recommend setting variables to be consumed by JavaScript using bracket notation like that. If you do not use that notation (i.e., greetings.message
and greetings.time
instead), your JavaScript will need to reference those keys from the JSON as MESSAGE
and TIME
(all caps). Unless you like turning caps lock on and off, you can see how that would get annoying after some time.
Assuming you already included jQuery in your application and you followed the code examples above, you now have a simple AJAX-powered web application built on Wheels. After clicking that Alert me!
link, your say controller will respond back to you the serialized message via AJAX. jQuery will parse the JSON object and populate the h1
and p
with the appropriate data.
That is it! Hopefully now you have a clearer picture on how to create AJAX-based features for your web applications.
With CommandBox, we don't need to have Lucee or Adobe ColdFusion installed locally. With a simple command, we can make CommandBox go and get the CFML engine we've requested, and quickly create a server running on Undertow. Make sure you're in the root of your website, and then run:
server start
The server will then start on a random port on 127.0.0.1
and will create a server.json
file in your webroot. We can add various options to server.json
to customize our server. Here's an example:
In this example, I've set the server name to myApp
, meaning I can now start the server from any directory by simply calling start myApp
. I've also specified a specific port, 60000
, but you can specify any port you want, or just remove that to start on a random port each time. Lastly, I've enabled URL rewriting and pointed the URL rewrite configuration file to urlrewrite.xml
, which is included starting from CFWheels 2.x. (If you've used the wheels new
command to create your app, this will already be done for you).
You can also specify hosts other than localhost: there's a useful CommandBox module to do that (Host updater) which will automatically create entries in your hosts file to allow for domains such as myapp.local
running on port 80. You can install it via install commandbox-hostupdater
when running the box shell with administrator privileges.
Obviously, anything you start, you might want to stop. Servers can be stopped either via right/ctrl clicking on the icon in the taskbar, or by the stop
command. To stop a server running in the current directory issue the following:
server stop
You can also stop a server from anywhere by using its name:
server stop myApp
If you want to see what server configurations exist on your system and their current status, simply do server list
server list
To remove a server configuration from the list, you can use server forget myapp
. Note the status of the servers on the list are somewhat unreliable, as it only remembers the last known state of the server: so if you start a server and then turn off your local machine, it may still remember it as running
when you turn your local machine back on, which is why we recommend the use of force: true
in the server.json
file.
By default, CommandBox will run Lucee (version 6.x at time of writing). You may wish to specify an exact version of Lucee, or use Adobe ColdFusion. We can do this via either setting the appropriate cfengine
setting in server.json
, or at runtime with the cfengine=
argument.
Start the default engine
CommandBox> start
__
Start the latest stable Lucee 5.x engine
CommandBox> start cfengine=lucee@5
__
Start a specific engine and version
CommandBox> start cfengine=adobe@10.0.12
__
Start the most recent Adobe server that starts with version "11"
CommandBox> start cfengine=adobe@11
__
Start the most recent adobe engine that matches the range
CommandBox> start cfengine="adobe@>9.0 <=11"
Or via server.json
CFIDE / Lucee administrators
The default username and password for all administrators is admin
& commandbox
You can of course run multiple servers, so if you need to test your app on Lucee 5.x, Lucee 6.x and Adobe 2018, you can just start three servers with different cfengine=
arguments!
Watch out
CommandBox 5.1 required to install dependencies easily
By default the Lucee server that CommandBox starts has all the basic Lucee extensions that you are going to need installed, but if need to minimize the size of the Lucee instance you launch, then you can use Lucee-Light by specifying cfengine=lucee-light
in your server.json
file. CFWheels can run just fine on lucee-light (which is after all, Lucee, minus all the extensions) but at a minimum, requires the following extensions to be installed as dependencies in your box.json
. Please note you may have to add any drivers you need for your database to this list as well.
Once added to your box.json file, while the server is running, just do box install
, which will install the dependencies, and load them into the running server within 60 seconds.
Alternatively you can download the extensions and add them manually to your server root's deploy folder (i.e \WEB-INF\lucee-server\deploy
)
In this tutorial, we'll be writing a simple application to make sure we have CFWheels installed properly and that everything is working as it should.
Let's make sure we're all on the same page. I'm going to assume that you've followed the Getting Started guide and have CommandBox all setup. If you haven't done that, stop and read that guide get everything setup. It's okay, this web page will wait for you.
Okay, so you have CFWheels installed and can see the CFWheels "Congratulations!" page as shown below. That wasn't that hard now, was it?
Okay, let's get to some example code. We know that you've been dying to get your hands on some code!
To continue with Programming Tutorial Tradition, we'll create the ubiquitous Hello World! application. But to keep things interesting, let's add a little CFWheels magic along the way.
Let's create a controller from scratch to illustrate how easy it is to set up a controller and plug it into the CFWheels framework.
First, create a file called Say.cfc
in the app/controllers
directory and add the
code below to the file.
Congratulations, you just created your first CFWheels controller! What does this
controller do, you might ask? Well, to be honest, not much. It has no methods
defined, so it doesn't add any new functionality to our application. But because
it extends the base Controller
component, it inherits quite a bit of powerful
functionality and is now tied into our CFWheels application.
So what happens if we try to call our new controller right now? Lets take a
look. Open your browser and point your browser to the new controller. Because my
local server is installed on port 60000, my URL is http://127.0.0.1:60000/say
.
You may need to enter a different URL, depending on how your web server is
configured. In my case, I'm using CommandBox.
The error says "Could not find the view page for the 'index' action in the 'say'
controller." Where did "index" come from? The URL we typed in only specified a
controller name but no action. When an action is not specified in the URL,
CFWheels assumes that we want the default action. Out of the box, the default
action in CFWheels is set to index
. So in our example, CFWheels tried to find
the index
action within the say
controller, and it threw an error because it
couldn't find its view page.
But let's jump ahead. Now that we have the controller created, let's add an
action to it called hello
. Change your say
controller so it looks like the
code block below:
As you can see, we created an empty method named hello
.
Now let's call our new action in the browser and see what we get. To call the
hello
action, we simply add /hello
to the end of the previous URL that we
used to call our say
controller:
http://127.0.0.1:60000/say/hello
Once again, we get a ColdFusion error. Although we have created the controller
and added the hello
action to it, we haven't created the view.
By default, when an action is called, CFWheels will look for a view file with
the same name as the action. It then hands off the processing to the view to
display the user interface. In our case, CFWheels tried to find a view file for
our say/hello
action and couldn't find one.
Let's remedy the situation and create a view file. View files are simple CFML pages that handle the output of our application. In most cases, views will return HTML code to the browser. By default, the view files will have the same name as our controller actions and will be grouped into a directory under the view directory. This new directory will have the same name as our controller.
Find the views
directory inside the app
directory, located at the root of your
CFWheels installation. There will be a few directories in there already. For
now, we need to create a new directory in the views
directory called say
.
This is the same name as the controller that we created above.
Now inside the say
directory, create a file called hello.cfm
. In the
hello.cfm
file, add the following line of code:
Save your hello.cfm
file, and let's call our say/hello
action once again.
You have your first working CFWheels page if your browser looks like Figure 3
below.
You have just created your first functional CFWheels page, albeit it is a very simple one. Pat yourself on the back, go grab a snack, and when you're ready, let's go on and extend the functionality of our Hello World! application a little more.
We will add some simple dynamic content to our hello
action and add a second
action to the application. We'll then use some CFWheels code to tie the 2
actions together. Let's get get to it!
The first thing we are going to do is to add some dynamic content to our
say/hello
action. Modify your say
controller so it looks like the code block
below:
All we are doing here is creating a variable called time
and setting its value
to the current server time using the basic ColdFusion Now()
function. When we
do this, the variable becomes immediately available to our view code.
Why not just set up this value directly in the view? If you think about it, maybe the logic behind the value of time may eventually change. What if eventually we want to display its value based on the user's time zone? What if later we decide to pull it from a web service instead? Remember, the controller is supposed to coordinate all of the data and business logic, not the view.
Next, we will modify our say/hello.cfm
view file so that it looks like the
code block below. When we do this, the value will be displayed in the browser.
call your say/hello
action again in your browser. Your browser should look
like Figure 4 below.
This simple example showed that any dynamic content created in a controller
action is available to the corresponding view file. In our application, we
created a time
variable in the say/hello
controller action and display that
variable in our say/hello.cfm
view file.
Now we will expand the functionality of our application once again by adding a
second action to our say
controller. If you feel adventurous, go ahead and add
a goodbye
action to the say
controller on your own, then create a
goodbye.cfm
view file that displays a "Goodbye" message to the user. If you're
not feeling that adventurous, we'll quickly go step by step.
First, modify the the say
controller file so that it looks like the code block
below.
Now go to the app/views/say
directory and create a goodbye.cfm
page.
Add the following code to the goodbye.cfm
page and save it.
If we did everything right, we should be able to call the new say/goodbye
action using the following URL:
http://127.0.0.1:60000/say/goodbye
Your browser should look like Figure 5 below:
Now let's link our two actions together. We will do this by adding a link to the bottom of each page so that it calls the other page.
Open the say/hello.cfm
view file. We are going to add a line of code to the
end of this file so our say/hello.cfm
view file looks like the code block
below:
The linkTo() function is a built-in CFWheels function. In this case, we are passing 2 named parameters to it. The first parameter, text
, is the text
that will be displayed in the hyperlink. The second parameter, action
, defines the action to point the link to. By using this built-in function, your application's main URL may change, and even controllers and actions may get shifted around, but you won't suffer from the dreaded dead link. CFWheels will
always create a valid link for you as long as you configure it correctly when you make infrastructure changes to your application.
Once you have added the additional line of code to the end of the
say/hello.cfm
view file, save your file and call the say/hello
action from
your browser. Your browser should look like Figure 6 below.
You can see that CFWheels created a link for us and added an appropriate URL for
the say/goodbye
action to the link.
Let's complete our little app and add a corresponding link to the bottom of our
say/goodbye.cfm
view page.
Open your say/goodbye.cfm
view page and modify it so it looks like the code
block below.
CFML: app/views/say/goodbye.cfm
If you now call the say/goodbye
action in your browser, your browser should
look like Figure 7 below.
You now know enough to be dangerous with CFWheels. Look out! But there are many more powerful features to cover. You may have noticed that we haven't even talked about the M in MVC.
No worries. We will get there. And we think you will enjoy it.