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
<cfoutput>
<div class="card">
<h3>#attributes.name#</h3>
<p>$#numberFormat(attributes.price, "0.00")#</p>
</div>
</cfoutput><cfloop query="products">
<cfmodule template="/templates/product-card.cfm" name="#products.name#" price="#products.price#">
</cfloop>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.
<cfloop array="#products#" index="p">
<cfmodule template="/templates/product-card.cfm" attributeCollection="#p#">
</cfloop>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
| Tool | Scope | Best for |
|---|---|---|
| cfinclude | Shared with caller | Static or mostly-static content, no per-instance data |
| Custom tag (cf_) | Isolated | A quick, occasional reusable block in a known location |
| cfimport | Isolated | A whole tag library used repeatedly, under your own prefix |
| cfmodule | Isolated | A tag called dynamically, by path, often per-row in a loop |
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.