--- name: contentbox-cfml-theme-development description: "Use this skill when creating or customizing ContentBox themes, including theme structure, metadata/settings, layout and view composition, collection templates, widget overrides, and theme lifecycle callbacks." applyTo: "**/*.{cfc,cfm,cfml}" --- # ContentBox Theme Development (CFML) Build custom themes for ContentBox CMS using CFML. Themes control the visual presentation of all public-facing content — blog entries, pages, archives, search results, and error pages. ## Theme Structure A ContentBox theme is a directory under `modules_app/contentbox-custom/_themes/` (custom) or `modules/contentbox/themes/` (core) containing: ``` MyTheme/ ├── Theme.cfc ← Theme metadata, settings, lifecycle callbacks ├── screenshot.png ← Theme preview image (shown in admin) ├── layouts/ │ ├── blog.cfm ← MANDATORY: Blog entry layout │ └── pages.cfm ← MANDATORY: Page layout │ ├── maintenance.cfm ← Optional: Maintenance mode layout │ └── search.cfm ← Optional: Search results layout (defaults to pages) ├── views/ │ ├── index.cfm ← MANDATORY: Home page (blog entry listing) │ ├── entry.cfm ← MANDATORY: Single blog entry with comments │ ├── page.cfm ← MANDATORY: Single page rendering │ ├── archives.cfm ← MANDATORY: Blog archives view │ ├── error.cfm ← MANDATORY: Error display │ ├── notfound.cfm ← Optional: Entry not found view │ └── maintenance.cfm ← Optional: Maintenance mode view ├── templates/ │ ├── entry.cfm ← Collection template for entry iterations │ ├── category.cfm ← Collection template for category iterations │ └── comment.cfm ← Collection template for comment iterations ├── widgets/ ← Theme-specific widget overrides │ └── MyWidget.cfc ← Overrides core widgets of the same name └── includes/ ← Help files, assets, etc. ``` ## Theme.cfc The `Theme.cfc` defines metadata, settings, and lifecycle callbacks: ```cfml component { // Theme Metadata this.name = "My Custom Theme"; this.description = "A beautiful custom theme for ContentBox"; this.version = "1.0.0"; this.author = "Your Name"; this.authorURL = "https://example.com"; this.screenShotURL = "screenshot.png"; // Theme Settings — array of setting structs this.settings = [ { name : "siteTitle", defaultValue : "My Site", type : "text", label : "Site Title:", required : true, group : "General" }, { name : "primaryColor", defaultValue : "#3b82f6", type : "color", label : "Primary Color:", group : "Colors" }, { name : "showSidebar", defaultValue : true, type : "boolean", label : "Show Sidebar:", group : "Layout" }, { name : "layoutStyle", defaultValue : "grid", type : "select", label : "Entry Layout:", options : "grid,list,masonry", group : "Layout" }, { name : "footerText", defaultValue : "", type : "textarea", label : "Footer Text:", group : "General" } ]; /** * Called when the theme is activated */ function onActivation(){ // Run setup logic, create default content, etc. } /** * Called when the theme is deactivated */ function onDeactivation(){ // Cleanup logic } /** * Called when the theme is deleted */ function onDelete(){ // Cleanup logic } } ``` ### Setting Types | Type | Description | |------|-------------| | `text` | Single-line text input (default) | | `textarea` | Multi-line text area | | `boolean` | Checkbox toggle | | `select` | Dropdown select box | | `color` | Color picker | ### Setting Struct Keys | Key | Required | Description | |-----|----------|-------------| | `name` | Yes | Setting name (saved as `cb_themeName_settingName`) | | `defaultValue` | Yes | Default value | | `type` | No | HTML control type (default: `text`) | | `label` | No | HTML label (defaults to `name`) | | `required` | No | Whether the setting is required (default: `false`) | | `title` | No | HTML title attribute | | `options` | No | For `select`: comma-separated list or array of values, or array of `{name, value}` structs | | `optionsUDF` | No | UDF name (no parentheses) that returns options, e.g., `getColors` | | `group` | No | Group name for organizing settings | | `groupIntro` | No | Description text for a group | | `fieldDescription` | No | Description for an individual field | | `fieldHelp` | No | HTML for a modal help popup (use `loadHelpFile()` helper) | ## Accessing Theme Settings in Views Theme settings are available via the `cb` helper: ```cfml #cb.getThemeSetting( "siteTitle" )# #cb.getThemeSetting( "primaryColor" )# #cb.getThemeSetting( "showSidebar", true )# ``` ## Layout Files ### blog.cfm — Blog Entry Layout ```cfml #cb.getContent().getTitle()# — #cb.getThemeSetting( "siteTitle" )# #renderView( view = "_assets/head" )# #renderView( view = "_assets/header" )#
#renderView()#
#renderView( view = "_assets/footer" )#
``` ### pages.cfm — Page Layout Similar structure to `blog.cfm`, used for rendering static pages. ## View Files ### index.cfm — Home Page ```cfml #cb.renderCollection( template = "entry", collection = prc.entries, counter = prc.start, totalItems = prc.totalRecords )# #cb.paginator( totalRecords = prc.totalRecords, maxRows = prc.maxRows, page = prc.page, pageLink = cb.siteURL() & "/page/{page}", align = "center" )# ``` ### entry.cfm — Single Blog Entry ```cfml

#entry.getTitle()#

By #entry.getAuthor().getFullName()# on #dateFormat( entry.getCreatedDate(), "mmmm d, yyyy" )#
#entry.getHTMLContent()#
#cb.renderCollection( template = "category", collection = entry.getCategories() )#
#cb.widget( "CommentForm" )#
``` ### page.cfm — Single Page ```cfml

#page.getTitle()#

#page.getHTMLContent()#
``` ### archives.cfm — Archives View ```cfml

Archives

#cb.renderCollection( template = "entry", collection = prc.entries )#
#cb.paginator( totalRecords = prc.totalRecords, maxRows = prc.maxRows, page = prc.page )#
``` ### error.cfm — Error Display ```cfml

Error

#prc.errorMessage ?: "An unexpected error occurred."#

Return Home
``` ## Collection Templates Templates in `templates/` are used with `cb.renderCollection()`. Each template receives: - `_counter` — Current iteration index (1-based) - `_items` — Total number of items in the collection - `{templateName}` — The object being rendered (e.g., `entry`, `category`, `comment`) ### templates/entry.cfm ```cfml

#entry.getTitle()#

#dateFormat( entry.getCreatedDate(), "mmmm d, yyyy" )# by #entry.getAuthor().getFullName()#
#entry.getHTMLContentExcerpt()#
``` ### templates/category.cfm ```cfml #category.getCategory()# ``` ### templates/comment.cfm ```cfml
#comment.getAuthor()#
#dateFormat( comment.getCreatedDate(), "mmmm d, yyyy" )#
#comment.getComment()#
``` ## Widget Overrides Place widgets in `widgets/` to override core widgets of the same name: ```cfml component extends="contentbox.models.ui.BaseWidget" singleton { function init(){ setName( "Menu" ); setVersion( "1.0.0" ); setDescription( "Custom menu widget override" ); } any function renderIt( string menuName = "main" ){ // Custom menu rendering } } ``` ## The CB Helper The `cb` helper (`CBHelper@contentbox`) is the primary API for theme development: ```cfml #cb.site()# #cb.siteURL()# #cb.siteName()# #cb.getContent()# #cb.entryURL( entry )# #cb.pageURL( page )# #cb.categoryURL( category )# #cb.getThemeSetting( "name" )# #cb.widget( "WidgetName", { arg1 = "value" } )# #cb.renderCollection( template = "entry", collection = query )# #cb.renderView( view = "partial" )# #cb.menu( "main" )# #cb.rssURL()# #cb.rssCommentsURL()# #cb.searchURL()# #cb.searchURL( "query" )# #cb.subscribeURL()# #cb.unsubscribeURL()# ``` ## Theme Discovery and Registration ContentBox discovers themes from two locations: 1. **Core themes**: `modules/contentbox/themes/` 2. **Custom themes**: `modules_app/contentbox-custom/_themes/` The `ThemeService@contentbox` builds the theme registry at startup. Custom themes override core themes of the same name. ## Theme Switching Themes can be switched per-site via admin settings. The active theme is resolved at runtime: ```cfml property name="themeService" inject="themeService@contentbox"; var activeTheme = themeService.getActiveTheme(); var themePath = themeService.getThemePath( activeTheme ); ``` ## Best Practices 1. **Always include mandatory files**: `Theme.cfc`, `blog.cfm`, `pages.cfm`, `index.cfm`, `entry.cfm`, `page.cfm`, `archives.cfm`, `error.cfm` 2. **Use `cb` helper** for all URL generation — never hardcode paths 3. **Use collection templates** for iterating over entries, categories, comments 4. **Group theme settings** logically using the `group` key 5. **Provide `screenshot.png`** for admin theme preview 6. **Use `loadHelpFile()`** for field help that reads from `includes/help/` 7. **Keep theme-specific widgets** in the theme's `widgets/` folder 8. **Test with multiple content types** — entries, pages, categories, search results 9. **Use `prc` scope** for handler-passed data in views 10. **Follow CFML compatibility** — target Lucee 5+ and Adobe ColdFusion 2018+ ## Engine Compatibility This skill targets **CFML engines** (Lucee 5+, Adobe ColdFusion 2018+). For BoxLang-specific syntax and features, see the BoxLang variant of this skill. Key CFML considerations: - Use `` for variable interpolation in CFML templates - Use `structKeyExists()` for safe struct access - Use `listContains()` for list operations - Use `arrayLen()` for array length - Use `dateFormat()` and `timeFormat()` for date formatting