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
cfparamthe 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
View file structure
Section titled “View file structure”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.
The default layout
Section titled “The default layout”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.
<!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.
Rendering the action view
Section titled “Rendering the action view”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.
Overriding layouts
Section titled “Overriding layouts”Three common overrides, from narrowest to widest scope:
- Per-action — call
renderView(layout="print")in the action. Usesapp/views/print.cfm. - Per-controller — call
usesLayout(template="admin")fromconfig(). Every action in the controller renders insideapp/views/admin.cfm. - No layout — pass
layout=falsetorenderView(orrenderPartial). The action view renders on its own, no outer shell. Useful for Turbo Frame responses and raw fragments.
component extends="Controller" { function config() { usesLayout(template="admin"); }}Inline interpolation in views
Section titled “Inline interpolation in views”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.
<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.
cfparam for safety
Section titled “cfparam for safety”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.
<cfparam name="posts" default=""><cfparam name="post" default="">Partials
Section titled “Partials”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.
<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.
Escaping and safety
Section titled “Escaping and safety”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.
<cfoutput> <p>##h(params.searchTerm)##</p></cfoutput>Common view helpers
Section titled “Common view helpers”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 againstpublic/images/.styleSheetLinkTag("app.css")— renders<link rel="stylesheet">againstpublic/stylesheets/.javaScriptIncludeTag("app.js")— renders<script src>againstpublic/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.