DevLearningTools

MODULE 9 · LESSON 06

Reusable Templates

Applying cfinclude and cfmodule beyond headers and footers — rendering a repeated UI piece from a loop with attributeCollection, and choosing the right reuse tool across everything this module covered.

New lessons are added one at a time as the course gets built out — a graded quiz for each lesson is still on the way.

Everything so far in this module has mostly used a page-level example: one header, one footer, one page reusing them. The same tools apply just as well to something rendered many times on a single page — a product card repeated for every row in a query, a list item repeated for every array element. This lesson applies cfinclude and cfmodule to that pattern, then closes out the module with a recap of when to reach for which tool.

Learning Objectives

After completing this lesson, you'll be able to:

  • Render a reusable template once per row of a query or array element, using cfmodule.
  • Pass each item's data into the template with attributeCollection instead of listing attributes individually.
  • Choose the right reuse tool — cfinclude, a custom tag, cfimport, or cfmodule — for a given situation.

Rendering a Template Once Per Row

CFScript — product-card.cfm
<cfoutput>
<div class="card">
    <h3>#attributes.name#</h3>
    <p>$#numberFormat(attributes.price, "0.00")#</p>
</div>
</cfoutput>
Tag Syntax — looping over a query
<cfloop query="products">
    <cfmodule template="/templates/product-card.cfm" name="#products.name#" price="#products.price#">
</cfloop>
NOTE

cfmodule is the right choice here, not cfinclude — each row needs its own isolated attributes rather than sharing one Variables scope across every iteration of the loop.

Passing a Whole Row at Once with attributeCollection

When the data is already a struct — an array of structs, or a row pulled out as one — attributeCollection avoids listing each field individually.

Tag Syntax
<cfloop array="#products#" index="p">
    <cfmodule template="/templates/product-card.cfm" attributeCollection="#p#">
</cfloop>
NOTE

This only works cleanly when the struct's keys already match the names the template expects in its Attributes scope (name, price) — attributeCollection maps keys straight across with no renaming step.

When cfinclude Still Wins

Not everything repeated on a page needs per-instance data. A static promo banner, a disclaimer, or a support-contact snippet reused identically everywhere doesn't need isolation or parameters at all — cfinclude is simpler and there's nothing for cfmodule's extra structure to buy here.

Choosing the Right Reuse Tool: A Module Recap

ToolScopeBest for
cfincludeShared with callerStatic or mostly-static content, no per-instance data
Custom tag (cf_)IsolatedA quick, occasional reusable block in a known location
cfimportIsolatedA whole tag library used repeatedly, under your own prefix
cfmoduleIsolatedA tag called dynamically, by path, often per-row in a loop
NOTE

All three isolated-scope options (custom tag, cfimport, cfmodule) run the same way once called — the difference is entirely in how the tag gets located and named at the call site, not in what it can do once running.

Common Beginner Mistakes

Using cfinclude inside a loop where each item needs different data

cfinclude has no parameter mechanism — inside a loop, that means setting a fresh variable before every single include call, which cfmodule's real attributes handle far more cleanly.

Assuming attributeCollection renames struct keys to match the template

It doesn't — the struct's keys become the Attributes scope's keys exactly as they are, so the struct and the template have to already agree on names.

Reaching for cfmodule for genuinely static, identical content

If nothing about the reused block changes per instance, cfinclude does the same job with less to write and nothing extra to isolate.

Best Practices

  • Match the tool to whether the reused content needs per-instance data, not just to whether it's reused at all.
  • Keep struct keys and template attribute names consistent from the start if attributeCollection is going to be used with them.
  • Default to cfinclude for identical, static content, and cfmodule the moment any per-instance variation shows up.

Interview Questions

Why use cfmodule instead of cfinclude inside a loop rendering many rows?

Each row needs its own isolated data. cfinclude shares one Variables scope across every iteration with no parameter mechanism, while cfmodule gives each call its own Attributes scope through real parameters.

What does attributeCollection require of the struct passed to it?

Its keys have to already match the names the template expects in Attributes — attributeCollection maps keys across as-is, with no renaming.

Across cfinclude, a custom tag, cfimport, and cfmodule, what's actually the deciding factor for which to use?

Whether the reused content needs isolation and per-instance parameters (ruling out cfinclude), and then how the tag is best located and named at the call site: a quick occasional tag (cf_ prefix), a whole tag library used constantly (cfimport with a prefix), or a tag identified dynamically by path or name (cfmodule).

Summary

In this lesson, you rendered a reusable template once per row of a query or array using cfmodule, passed row data in with attributeCollection, and recapped when each of this module's reuse tools — cfinclude, custom tags, cfimport, and cfmodule — is the right fit. That wraps up Reusable Code.

What's Next?

The next module covers Database — datasources, cfquery, queryExecute(), cfqueryparam, CRUD operations, and transactions.