Skip to content

Basics

Views, Layouts, Partials

A view is a .cfm template that runs after your action finishes. It renders inside a layout, can include partials, and reads whatever variables the controller set. This page shows you the directory layout, how the default layout wraps every view, how to override it, and how partials let you stop repeating yourself.

You’ll learn:

  • Where view files live and what the underscore prefix means
  • What the default layout does and how to override it per-action or per-controller
  • How controller variables reach the view — and why every view should cfparam the ones it reads
  • How to extract a partial and pass data into it
  • Which helpers to reach for when you need a link, an image, or an asset tag

Views live under app/views/, one folder per controller. Action views take the action’s name (index.cfm, show.cfm). Partials start with an underscore. A shared/ folder is the convention for partials that cross controller boundaries.

  • Directoryapp/views/
    • layout.cfm default layout wraps every view
    • Directoryposts/
      • index.cfm Posts#index renders this
      • show.cfm
      • new.cfm
      • edit.cfm
      • _form.cfm shared form partial (underscore prefix convention)
    • Directorycomments/
      • _comment.cfm shared comment partial
    • Directoryshared/
      • _header.cfm cross-controller partial

The underscore isn’t decorative — it’s how Wheels tells a partial apart from an action view. You never type the underscore when including the partial; the framework adds it back.

app/views/layout.cfm is the outer shell every response renders inside. It’s a normal CFML file that calls includeContent() where the action view’s output should land. Put the header, footer, navigation, and any always-on UI chrome here.

app/views/layout.cfm
<!doctype html>
<html>
<head>
<title>My App</title>
</head>
<body>
<header>...</header>
<main>
<cfoutput>##includeContent()##</cfoutput>
</main>
<footer>...</footer>
</body>
</html>

includeContent() places the action view’s output into the layout.

After the controller action returns, Wheels looks for a view matching the controller and action. Posts#index renders app/views/posts/index.cfm. You don’t call a render function unless you want something other than the default — see Controllers and Actions for renderView(action="summary") and the other overrides.

Three common overrides, from narrowest to widest scope:

  • Per-action — call renderView(layout="print") in the action. Uses app/views/print.cfm.
  • Per-controller — call usesLayout(template="admin") from config(). Every action in the controller renders inside app/views/admin.cfm.
  • No layout — pass layout=false to renderView (or renderPartial). The action view renders on its own, no outer shell. Useful for Turbo Frame responses and raw fragments.
app/controllers/Admin/Posts.cfc
component extends="Controller" {
function config() {
usesLayout(template="admin");
}
}

Variables the controller sets become variables in the view — no explicit passing, no assigns[] bag. If the action ran posts = model("Post").findAll(), the view reads posts directly.

app/views/posts/show.cfm
<cfoutput>
<h1>##post.title##</h1>
<p>##post.body##</p>
<p>Posted <em>##DateFormat(post.publishedAt, "long")##</em></p>
</cfoutput>

Finders return a query object (or a model instance for findByKey/findOne). Loop over queries with <cfloop query="posts">, not <cfloop array="#posts#"> — query objects aren’t arrays.

Every variable a view reads should have a cfparam at the top of the file. If the controller forgets to set one — or a partial ends up rendered from a different action — the view won’t blow up with “variable undefined.” It renders empty instead, which is recoverable.

app/views/posts/index.cfm
<cfparam name="posts" default="">
<cfparam name="post" default="">

Partials are the reuse mechanism for view code. A partial file starts with an underscore — _form.cfm — but the include call leaves the underscore off: includePartial(partial="form"). Wheels finds app/views/<currentController>/_form.cfm.

Partial scope is isolated. The partial doesn’t see the surrounding view’s local variables automatically — pass them as named arguments and they arrive as local variables inside the partial.

app/views/comments/index.cfm
<cfoutput>
##includePartial(partial="comment", comment=myComment)##
</cfoutput>

Inside app/views/comments/_comment.cfm, the named argument comment is available as a local variable — comment.body, comment.author, and so on. Cross-controller partials take a path: includePartial(partial="shared/header") loads app/views/shared/_header.cfm.

renderPartial is the controller-side equivalent — use it from an action when you want to return a partial as the full response (Turbo Frame swaps, AJAX updates). Same partial file, same arguments; the difference is who calls it. See Controllers and Actions.

Anything a user typed — a search term, a comment body, a profile bio — must be escaped before it lands in HTML. The h() helper wraps EncodeForHTML and is the one-character way to do it in a view: h(userSubmittedText).

Wheels 4.0 form helpers escape their input by default (see Forms and Form Helpers). Outside the form helpers, escaping is on you. Never interpolate user input into a view unescaped — that’s the classic XSS gap.

app/views/search/show.cfm
<cfoutput>
<p>##h(params.searchTerm)##</p>
</cfoutput>

The helpers you’ll reach for most often in view code:

  • linkTo(route=..., key=..., text=..., class=...) — renders an <a> tag with the URL filled in from a named route.
  • urlFor(route=..., key=...) — returns just the URL string. Use when you need the URL outside an anchor (JavaScript, form action, Location: header).
  • imageTag("logo.png", alt="Logo") — renders <img> with the path resolved against public/images/.
  • styleSheetLinkTag("app.css") — renders <link rel="stylesheet"> against public/stylesheets/.
  • javaScriptIncludeTag("app.js") — renders <script src> against public/javascripts/.
  • h(value) — HTML-escape a string before interpolating it.

Form and field helpers (textField, emailField, submitTag, and friends) get their own page — see Forms and Form Helpers.