Components
BoxLang components are reusable blocks of code that extend the language's capabilities without modifying the parser.
BoxLang components are reusable blocks of code that extend the language's capabilities without modifying the parser. They provide a powerful way to create custom language constructs, encapsulate complex logic, and build modular applications. They are analogous to web components. Here is a super simple example of a component that outputs a greeting:
Greeting.bxm
<bx:output>
<h1>Hello, #attributes.yourname ?: "None Passed"#!</h1>
</bx:output>
Now I can call it in my BoxLang script:
// In a BoxLang script
bx:_greeting yourname="World";
Or in a BoxLang template:
<bx:_greeting yourname="World">
This will output:
<h1>Hello, World!</h1>
This simple example illustrates how components allow you to encapsulate functionality and reuse it across your BoxLang applications. Components can be as simple or complex as needed, and they can include attributes, logic, and even nested content.
Why Components Matter
Components solve common development challenges:
Eliminate Code Duplication: Write once, use everywhere
Extend the Language: Create custom block constructs that feel native
Encapsulate Logic: Keep complex operations contained and testable
Cross-Context Usage: BoxLang components work seamlessly in both script and template contexts
Basic Principles
Component Syntax
Components can be called using either script-based or template-based syntax according to where they are being used:
Script Context:
// Self-closing
bx:_myComponent;
bx:_myComponent attribute="value";
// With body content
bx:_myComponent attribute="value" {
// Content and logic here
}
Template Context:
<!-- With no body -->
<bx:_myComponent attribute="value">
<!-- With body content -->
<bx:_myComponent attribute="value">
Content and logic here
</bx:_myComponent>
Component Execution Flow
When BoxLang encounters a component call, it follows this process:
Core and Module Components
Core Components
BoxLang ships with many core components that extend the language with framework capabilities. You can find them in the reference section.
// HTTP operations
bx:http url="https://api.example.com/users" result="apiResponse";
// Database queries
bx:query name="users" datasource="myDB" {
writeoutput( "SELECT id, name, email FROM users WHERE active = 1 " )
}
// File operations
bx:file action="read" file="/path/to/data.txt" variable="fileContent";
Module Components
Any BoxLang module can also register and collaborate with components to the runtime.
// Example: A caching module might provide
bx:cache key="userList" timeout="3600" {
// Expensive operation cached for 1 hour
bx:query name="expensiveQuery" datasource="myDB" {
writeOutput( "SELECT * FROM complex_view WHERE processing_intensive = 1" )
}
}
// Example: A PDF module might provide
bx:pdf action="generate" filename="report.pdf" {
// PDF content here
}
Creating Custom Components
Custom components are user-defined components written in BoxLang that will allow you to extend the language with your own features. It will also allow you to create expressive contributions to the templating language.
Custom components can be created using two approaches:
Template-based components (
.bxm
files): Use BoxLang's templating syntaxScript-based components (
.bxs
files): Use pure BoxLang script syntax
Basic Custom Component Structure
Let's start with simple examples using both approaches:
Template-based Component
File: components/greeting.bxm
<!--
Simple greeting component that takes a name attribute
Usage: <bx:greeting name="Alice" />
-->
<div class="greeting-card">
<h2>Hello, #attributes.yourname ?: "World"#!</h2>
<p>Welcome to our application.</p>
</div>
Script-based Component
File: components/greeting.bxs
/*
Simple greeting component that takes a name attribute
Usage: bx:greeting name="Alice";
*/
writeOutput( '<div class="greeting-card">' );
writeOutput( '<h2>Hello, ' & ( attributes.yourname ?: "World" ) & '!</h2>' );
writeOutput( '<p>Welcome to our application.</p>' );
writeOutput( '</div>' );
Component Input: The attributes
Scope
attributes
ScopeAll data passed to your component is available in the attributes
scope. Both template and script-based components can validate and process these attributes. Note that the attribute name name
is reserved and is only used when invoking components via bx:component
.
Template-based Component with Attributes
<!-- File: components/userCard.bxm -->
<!-- Best practice: Parameterize your attributes with validation -->
<bx:param name="attributes.userId" type="string" required="true">
<bx:param name="attributes.username" type="string" required="true">
<bx:param name="attributes.email" type="string" required="true">
<bx:param name="attributes.showAvatar" type="boolean" default="false">
<bx:param name="attributes.theme" type="string" default="light">
<div class="user-card theme-#attributes.theme#" data-user-id="#attributes.userId#">
<bx:if attributes.showAvatar>
<img src="/avatars/#attributes.userId#.jpg" alt="Avatar" class="avatar" />
</bx:if>
<div class="user-info">
<h3>#attributes.username#</h3>
<p class="email">#attributes.email#</p>
</div>
</div>
Script-based Component with Attributes
// File: components/userCard.bxs
// Best practice: Parameterize your attributes with validation
bx:param name="attributes.userId" type="string" required="true";
bx:param name="attributes.username" type="string" required="true";
bx:param name="attributes.email" type="string" required="true";
bx:param name="attributes.showAvatar" type="boolean" default="false";
bx:param name="attributes.theme" type="string" default="light";
writeOutput( '<div class="user-card theme-' & attributes.theme & '" data-user-id="' & attributes.userId & '">' );
if ( attributes.showAvatar ) {
writeOutput( '<img src="/avatars/' & attributes.userId & '.jpg" alt="Avatar" class="avatar" />' );
}
writeOutput( '<div class="user-info">' );
writeOutput( '<h3>' & attributes.username & '</h3>' );
writeOutput( '<p class="email">' & attributes.email & '</p>' );
writeOutput( '</div>' );
writeOutput( '</div>' );
Component Content: The thisComponent
Scope
thisComponent
ScopeWhen components have start and end tags, BoxLang provides the thisComponent
scope to manage content and execution. This works the same way in both template and script-based components.
The thisComponent
scope contains:
executionMode
: "start" or "end"hasEndTag
: boolean indicating if component has closing taggeneratedContent
: Content between start and end tags
Template-based Component with thisComponent
thisComponent
File: components/boldWrapper.bxm
<!-- Component that wraps content in bold tags -->
<bx:if thisComponent.executionMode eq "end">
<bx:output><b>#thisComponent.generatedContent#</b></bx:output>
<bx:set thisComponent.generatedContent = "">
</bx:if>
Script-based Component with thisComponent
thisComponent
File: components/boldWrapper.bxs
// Component that wraps content in bold tags
if ( thisComponent.executionMode == "end" ) {
writeOutput( "<b>" & thisComponent.generatedContent & "</b>" );
thisComponent.generatedContent = "";
}
Usage (same for both):
<bx:boldWrapper>This text will be bold</bx:boldWrapper>
<!-- Outputs: <b>This text will be bold</b> -->
Complex Component with Start/End Logic
Here's an advanced component demonstrating the full execution cycle, shown in both template and script syntax:
Template-based Version
File: components/section.bxm
<!-- Advanced component demonstrating full execution cycle -->
<bx:param name="attributes.title" type="string" required="true">
<bx:param name="attributes.collapsible" type="boolean" default="false">
<bx:param name="attributes.collapsed" type="boolean" default="false">
<bx:if thisComponent.executionMode eq "start">
<!-- Opening section markup -->
<section class="content-section">
<header class="section-header">
<h2>#attributes.title#</h2>
<bx:if thisComponent.attributes.collapsible>
<button class="toggle-btn" data-collapsed="#attributes.collapsed#">
#attributes.collapsed ? "Expand" : "Collapse"#
</button>
</bx:if>
</header>
<div class="section-content"
style="#attributes.collapsed ? 'display:none' : ''#">
</bx:if>
<bx:if thisComponent.executionMode eq "end">
<!-- Closing section markup -->
</div>
</section>
</bx:if>
Script-based Version
File: components/section.bxs
// Advanced component demonstrating full execution cycle
bx:param name="attributes.title" type="string" required="true";
bx:param name="attributes.collapsible" type="boolean" default="false";
bx:param name="attributes.collapsed" type="boolean" default="false";
if ( thisComponent.executionMode == "start" ) {
// Opening section markup
writeOutput( '<section class="content-section">' );
writeOutput( '<header class="section-header">' );
writeOutput( '<h2>' & attributes.title & '</h2>' );
if ( attributes.collapsible ) {
writeOutput( '<button class="toggle-btn" data-collapsed="' & attributes.collapsed & '">' );
writeOutput( attributes.collapsed ? "Expand" : "Collapse" );
writeOutput( '</button>' );
}
writeOutput( '</header>' );
writeOutput( '<div class="section-content"' );
if ( attributes.collapsed ) {
writeOutput( ' style="display:none"' );
}
writeOutput( '>' );
}
if ( thisComponent.executionMode == "end" ) {
// Closing section markup
writeOutput( '</div>' );
writeOutput( '</section>' );
}
Usage (same for both template and script versions):
<bx:_section title="User Information" collapsible="true">
<p>This content appears inside the section.</p>
<bx:userCard userId="123" name="John Doe" email="[email protected]" />
</bx:_section>
Custom Component Discovery
Custom component discovery is a hierarchical lookup process that occurs when BoxLang encounters a component call that isn't a registered core or module component.
Discovery Configuration
BoxLang component locations can be defined globally or on a per-app basis.
Global Configuration (boxlang.json
):
boxlang.json
):{
"customComponentsDirectory": [
"${boxlang-home}/global/components"
],
"classPaths": [
"${boxlang-home}/global/classes"
]
}
Application Configuration (Application.bx
):
Application.bx
):class {
// Component paths for .bxm, .bxs template files
this.customComponentPaths = [
"/absolute/path/to/components",
"./relative/path/components"
];
// Class paths for .bx class files
this.classPaths = [
"/absolute/path/to/classes"
];
}
File Extensions Searched
During discovery, BoxLang looks for files with these extensions in order:
.bxm
(BoxLang template).bxs
(BoxLang script).cfc
(CFML component - for compatibility).cfm
(CFML template - for compatibility)
Calling Custom Components
Method 1: Using bx:component
bx:component
Script Syntax:
// Basic call
bx:_component template="greeting" username="Alice";
// With body content
bx:_component template="userCard" userId="123" username="John Doe" {
writeOutput( "<p>Additional content here</p>" );
}
// With relative or absolute paths
bx:_component template="./components/greeting" username="Alice";
bx:_component template="/shared/components/layout" title="My Page";
Template Syntax:
<!-- Basic call -->
<bx:_component template="greeting" username="Alice" />
<!-- With body content -->
<bx:_component template="userCard" userId="123" username="John Doe">
<p>Additional content here</p>
</bx:_component>
Method 2: Convention-Based Calling
BoxLang looks for a component file matching the name after bx:
:
Script Syntax:
// Looks for greeting.bxm, greeting.bxs, etc.
bx:_greeting name="Alice";
bx:_userCard userId="123" username="John Doe" {
writeOutput( "<p>Additional content</p>" );
}
Template Syntax:
<!-- Looks for greeting.bxm, greeting.bxs, etc. -->
<bx:_greeting username="Alice" />
<bx:_userCard userId="123" username="John Doe">
<p>Additional content</p>
</bx:_userCard>
Component Scopes Deep Dive
The attributes
Scope
attributes
ScopeContains all attributes passed to the component call:
<!-- Component: productDisplay.bxm -->
<bx:param name="attributes.productId" type="string" required="true">
<bx:param name="attributes.showPrice" type="boolean" default="true">
<bx:param name="attributes.currency" type="string" default="USD">
<div class="product" data-id="#attributes.productId#">
<h3>#attributes.username#</h3>
<bx:if "#attributes.showPrice#">
<p class="price">#attributes.price# #attributes.currency#</p>
</bx:if>
</div>
The variables
Scope
variables
ScopeA localized scope for the component's internal logic:
<!-- Component: calculator.bxm -->
<bx:param name="attributes.operation" type="string" required="true">
<bx:param name="attributes.a" type="numeric" required="true">
<bx:param name="attributes.b" type="numeric" required="true">
<bx:switch expression="#attributes.operation#">
<bx:case value="add">
<bx:set variables.result = attributes.a + attributes.b />
</bx:case>
<bx:case value="multiply">
<bx:set variables.result = attributes.a * attributes.b />
</bx:case>
<bx:defaultcase>
<bx:set variables.result = "Invalid operation" />
</bx:defaultcase>
</bx:switch>
<div class="calculation-result">
<p>Result: #variables.result#</p>
</div>
The caller
Scope
caller
ScopeProvides access to the calling context (use sparingly):
<!-- Component: debugInfo.bxm -->
<bx:if "#isDefined( 'caller.request.debug' ) AND caller.request.debug#">
<div class="debug-panel">
<h4>Debug Information</h4>
<p>Current Template: #caller.getCurrentTemplatePath()#</p>
<p>Variables Count: #structCount( caller.variables )#</p>
</div>
</bx:if>
The thisComponent
Scope
thisComponent
ScopeManages component execution and content:
<!-- Component: accordion.bxm -->
<bx:param name="attributes.title" type="string" required="true">
<bx:param name="attributes.expanded" type="boolean" default="false">
<bx:if thisComponent.executionMode eq "start">
<div class="accordion-item">
<button class="accordion-header" onclick="toggleAccordion(this)">
#attributes.title#
</button>
<div class="accordion-content" style="#attributes.expanded ? '' : 'display:none'#">
</bx:if>
<bx:if thisComponent.executionMode eq "end">
</div>
</div>
</bx:if>
Associating Subtag Data with Base Tags
BoxLang provides the bx:associate
component to create relationships between child components and their parent components. This powerful feature allows you to build hierarchical component structures where child components can pass data up to their parent components.
How Component Association Works
When you use bx:associate
inside a child component, it creates a data structure in the parent component that contains all the associated data from its children. This enables complex component hierarchies like forms with fields, menus with items, or layouts with sections.
Basic Association Syntax
Script Syntax:
bx:associate dataCollection="collectionName";
Template Syntax:
<bx:associate dataCollection="collectionName" />
Simple Example: Menu with Menu Items
Parent Component: menu.bxm
<!-- File: components/menu.bxm -->
<bx:param name="attributes.id" type="string" required="true">
<bx:param name="attributes.class" type="string" default="nav-menu">
<bx:if thisComponent.executionMode eq "start">
<nav id="#attributes.id#" class="#attributes.class#">
<ul class="menu-list">
</bx:if>
<bx:if thisComponent.executionMode eq "end">
<!-- Now render all associated menu items -->
<bx:if "#isDefined( 'thisComponent.menuItems' ) AND isArray( thisComponent.menuItems )#">
<bx:loop array="#thisComponent.menuItems#" index="menuItem">
<li class="menu-item">
<a href="#menuItem.url#"
class="#menuItem.class ?: ''#"
#menuItem.target ? 'target="' & menuItem.target & '"' : ''#>
#menuItem.label#
</a>
</li>
</bx:loop>
</bx:if>
</ul>
</nav>
</bx:if>
Child Component: menuItem.bxm
<!-- File: components/menuItem.bxm -->
<bx:param name="attributes.label" type="string" required="true">
<bx:param name="attributes.url" type="string" required="true">
<bx:param name="attributes.class" type="string" default="">
<bx:param name="attributes.target" type="string" default="">
<!-- Associate this menu item data with the parent menu -->
<bx:associate dataCollection="menuItems" />
Usage:
<bx:_menu id="mainNav" class="primary-navigation">
<bx:_menuItem label="Home" url="/" />
<bx:_menuItem label="About" url="/about" />
<bx:_menuItem label="Products" url="/products" class="dropdown-trigger" />
<bx:_menuItem label="Contact" url="/contact" />
<bx:_menuItem label="External Link" url="https://example.com" target="_blank" />
</bx:_menu>
Advanced Example: Form with Form Fields
Parent Component: form.bxm
<!-- File: components/form.bxm -->
<bx:param name="attributes.action" type="string" required="true">
<bx:param name="attributes.method" type="string" default="POST">
<bx:param name="attributes.id" type="string" default="">
<bx:param name="attributes.class" type="string" default="form">
<bx:param name="attributes.validateOnSubmit" type="boolean" default="true">
<bx:if thisComponent.executionMode eq "start">
<form action="#attributes.action#"
method="#attributes.method#"
#len( attributes.id ) ? 'id="' & attributes.id & '"' : ''#
class="#attributes.class#"
#attributes.validateOnSubmit ? 'data-validate="true"' : ''#>
</bx:if>
<bx:if thisComponent.executionMode eq "end">
<!-- Render all associated form fields -->
<bx:if "#isDefined( 'thisComponent.formFields' ) AND isArray( thisComponent.formFields )#">
<bx:loop array="#thisComponent.formFields#" index="field">
<div class="form-group field-type-#field.type#">
<bx:if "#len( field.label ?: '' )#">
<label for="#field.fieldname#" class="form-label">
#field.label#
<bx:if "#field.required#">
<span class="required">*</span>
</bx:if>
</label>
</bx:if>
<bx:switch expression="#field.type#">
<bx:case value="text,email,password,number,tel,url">
<input type="#field.type#"
name="#field.fieldname#"
id="#field.name#"
value="#field.value ?: ''#"
class="form-control #field.class ?: ''#"
#field.required ? 'required' : ''#
#len( field.placeholder ?: '' ) ? 'placeholder="' & field.placeholder & '"' : ''# />
</bx:case>
<bx:case value="textarea">
<textarea name="#field.fieldname#"
id="#field.fieldname#"
class="form-control #field.class ?: ''#"
#field.required ? 'required' : ''#
#len( field.placeholder ?: '' ) ? 'placeholder="' & field.placeholder & '"' : ''#
rows="#field.rows ?: 4#">#field.value ?: ''#</textarea>
</bx:case>
<bx:case value="select">
<select name="#field.fieldname#"
id="#field.fieldname#"
class="form-control #field.class ?: ''#"
#field.required ? 'required' : ''#>
<bx:if "#len( field.placeholder ?: '' )#">
<option value="">#field.placeholder#</option>
</bx:if>
<bx:if "#isDefined( 'field.options' ) AND isArray( field.options )#">
<bx:loop array="#field.options#" index="option">
<option value="#option.value#"
#option.value EQ ( field.value ?: '' ) ? 'selected' : ''#>
#option.label#
</option>
</bx:loop>
</bx:if>
</select>
</bx:case>
</bx:switch>
<bx:if "#len( field.helpText ?: '' )#">
<small class="form-help">#field.helpText#</small>
</bx:if>
</div>
</bx:loop>
</bx:if>
<!-- Render any additional content (like buttons) -->
<bx:if "#isDefined( 'thisComponent.formActions' ) AND isArray( thisComponent.formActions )#">
<div class="form-actions">
<bx:loop array="#thisComponent.formActions#" index="action">
<button type="#action.type ?: 'button'#"
class="btn #action.class ?: 'btn-primary'#"
#len( action.onclick ?: '' ) ? 'onclick="' & action.onclick & '"' : ''#>
#action.label#
</button>
</bx:loop>
</div>
</bx:if>
</form>
</bx:if>
Child Components:
formField.bxm
<!-- File: components/formField.bxm -->
<bx:param name="attributes.fieldname" type="string" required="true">
<bx:param name="attributes.type" type="string" default="text">
<bx:param name="attributes.label" type="string" default="">
<bx:param name="attributes.value" type="string" default="">
<bx:param name="attributes.placeholder" type="string" default="">
<bx:param name="attributes.required" type="boolean" default="false">
<bx:param name="attributes.class" type="string" default="">
<bx:param name="attributes.helpText" type="string" default="">
<bx:param name="attributes.rows" type="numeric" default="4">
<!-- Associate this field data with the parent form -->
<bx:associate dataCollection="formFields" />
formOption.bxm
<!-- File: components/formOption.bxm -->
<bx:param name="attributes.value" type="string" required="true">
<bx:param name="attributes.label" type="string" required="true">
<!-- Associate this option with the parent form field -->
<bx:associate dataCollection="options" />
formAction.bxm
<!-- File: components/formAction.bxm -->
<bx:param name="attributes.label" type="string" required="true">
<bx:param name="attributes.type" type="string" default="submit">
<bx:param name="attributes.class" type="string" default="btn-primary">
<bx:param name="attributes.onclick" type="string" default="">
<!-- Associate this action with the parent form -->
<bx:associate dataCollection="formActions" />
Usage:
<bx:_form action="/contact/submit" method="POST" id="contactForm">
<bx:_formField fieldname="firstName"
type="text"
label="First Name"
required="true"
placeholder="Enter your first name" />
<bx:_formField fieldname="email"
type="email"
label="Email Address"
required="true"
helpText="We'll never share your email" />
<bx:_formField fieldname="country"
type="select"
label="Country"
required="true"
placeholder="Select your country">
<bx:_formOption value="us" label="United States" />
<bx:_formOption value="ca" label="Canada" />
<bx:_formOption value="uk" label="United Kingdom" />
</bx:_formField>
<bx:_formField fieldname="message"
type="textarea"
label="Message"
placeholder="Enter your message"
rows="6" />
<bx:_formAction label="Send Message" type="submit" />
<bx:_formAction label="Reset Form" type="reset" class="btn-secondary" />
</bx:_form>
Key Points About bx:associate
bx:associate
1. Data Collection Names
The dataCollection
attribute specifies the name of the array that will be created in the parent component's thisComponent
scope.
2. Automatic Array Creation
If the specified collection doesn't exist, BoxLang automatically creates it as an empty array.
3. Attribute Inheritance
All attributes from the child component are automatically added to the collection item.
4. Execution Timing
Association happens during the child component's execution, so data is available when the parent reaches its "end" execution mode.
5. Nested Associations
You can have multiple levels of association for complex hierarchies.
Advanced Pattern: Tab Container
Parent Component: tabContainer.bxm
<bx:param name="attributes.id" type="string" required="true">
<bx:param name="attributes.activeTab" type="string" default="">
<bx:if thisComponent.executionMode eq "start">
<div id="#attributes.id#" class="tab-container">
<ul class="tab-nav" role="tablist">
</bx:if>
<bx:if thisComponent.executionMode eq "end">
<!-- Render tab navigation -->
<bx:if "#isDefined( 'thisComponent.tabs' ) AND isArray( thisComponent.tabs )#">
<bx:loop array="#thisComponent.tabs#" index="tab" item="i">
<li class="tab-nav-item">
<button class="tab-button #( i EQ 1 OR tab.id EQ attributes.activeTab ) ? 'active' : ''#"
data-tab="#tab.id#"
role="tab">
#tab.title#
</button>
</li>
</bx:loop>
</ul>
<!-- Render tab content -->
<div class="tab-content">
<bx:loop array="#thisComponent.tabs#" index="tab" item="i">
<div id="#tab.id#"
class="tab-pane #( i EQ 1 OR tab.id EQ attributes.activeTab ) ? 'active' : ''#"
role="tabpanel">
#tab.content#
</div>
</bx:loop>
</div>
</bx:if>
</div>
</bx:if>
Child Component: tab.bxm
<bx:param name="attributes.id" type="string" required="true">
<bx:param name="attributes.title" type="string" required="true">
<bx:if thisComponent.executionMode eq "end">
<!-- Capture the tab content -->
<bx:set variables.tabContent = thisComponent.generatedContent />
<bx:set thisComponent.generatedContent = "">
<!-- Set the content in attributes for association -->
<bx:set attributes.content = variables.tabContent />
</bx:if>
<!-- Associate this tab with the parent container -->
<bx:associate dataCollection="tabs" />
Usage:
<bx:_tabContainer id="mainTabs" activeTab="profile">
<bx:_tab id="overview" title="Overview">
<h3>Account Overview</h3>
<p>Welcome to your account dashboard.</p>
</bx:_tab>
<bx:_tab id="profile" title="Profile">
<h3>Profile Settings</h3>
<bx:_form action="/profile/update">
<bx:_formField name="name" label="Full Name" required="true" />
<bx:_formAction label="Update Profile" />
</bx:_form>
</bx:tab>
<bx:_tab id="settings" title="Settings">
<h3>Account Settings</h3>
<p>Manage your account preferences.</p>
</bx:_tab>
</bx:_tabContainer>
This association pattern enables powerful component composition where child components contribute data to their parents, creating flexible and reusable component hierarchies.
Advanced Component Patterns
Component Composition
Components can call other components for powerful composition:
<!-- Component: pageLayout.bxm -->
<bx:param name="attributes.title" type="string" required="true">
<bx:param name="attributes.showSidebar" type="boolean" default="true">
<bx:if thisComponent.executionMode eq "start">
<!DOCTYPE html>
<html>
<head>
<title>#attributes.title#</title>
<bx:stylesheet href="/css/main.css" >
</head>
<body>
<bx:_header siteName="My Site" >
<div class="main-container">
<bx:if "#attributes.showSidebar#">
<aside class="sidebar">
<bx:_navigation >
</aside>
</bx:if>
<main class="content">
</bx:if>
<bx:if thisComponent.executionMode eq "end">
</main>
</div>
<bx:_footer />
</body>
</html>
</bx:if>
Conditional Component Loading
<!-- Component: roleBasedContent.bxm -->
<bx:param name="attributes.userRole" type="string" required="true">
<bx:switch expression="#attributes.userRole#">
<bx:case value="admin">
<bx:adminDashboard userId="#attributes.userId#" />
</bx:case>
<bx:case value="moderator">
<bx:moderatorPanel userId="#attributes.userId#" />
</bx:case>
<bx:defaultcase>
<bx:userDashboard userId="#attributes.userId#" />
</bx:defaultcase>
</bx:switch>
Best Practices
1. Always Use bx:param
for Attribute Validation
bx:param
for Attribute ValidationTemplate-based components:
<!-- Good: Explicit parameter definition -->
<bx:param name="attributes.userId" type="string" required="true">
<bx:param name="attributes.maxItems" type="numeric" default="10">
<!-- Avoid: Accessing attributes without validation -->
<!-- <p>User: #attributes.userId#</p> -->
Script-based components:
// Good: Explicit parameter definition
bx:param name="attributes.userId" type="string" required="true";
bx:param name="attributes.maxItems" type="numeric" default="10";
// Avoid: Accessing attributes without validation
// writeOutput( "User: " & attributes.userId );
2. Use Descriptive Component Names
<!-- Good -->
<bx:_userProfileCard userId="123" />
<bx:_productListingGrid products="#variables.products#" />
<!-- Avoid -->
<bx:_card data="123" />
<bx:_list items="#variables.items#" />
3. Document Your Components
Template-based components:
<!--
Component: userProfileCard.bxm
Description: Displays a user profile with avatar, name, and contact info
Attributes:
- userId (string, required): User's unique identifier
- showEmail (boolean, default: true): Whether to show email address
- theme (string, default: "light"): Visual theme (light|dark)
Example:
```xml
<!--
Component: userProfileCard.bxm
Description: Displays a user profile with avatar, name, and contact info
Attributes:
- userId (string, required): User's unique identifier
- showEmail (boolean, default: true): Whether to show email address
- theme (string, default: "light"): Visual theme (light|dark)
Example:
<bx:userProfileCard userId="123" showEmail="false" theme="dark" />
-->
Script-based components:
/*
Component: userProfileCard.bxs
Description: Displays a user profile with avatar, name, and contact info
Attributes:
- userId (string, required): User's unique identifier
- showEmail (boolean, default: true): Whether to show email address
- theme (string, default: "light"): Visual theme (light|dark)
Example:
bx:userProfileCard userId="123" showEmail="false" theme="dark";
*/
4. Minimize Use of caller
Scope
caller
ScopeTemplate-based components:
<!-- Good: Self-contained component -->
<bx:param name="attributes.data" type="array" required="true">
<!-- Avoid: Reaching into caller scope -->
<!-- <bx:set variables.data = caller.variables.someData> -->
Script-based components:
// Good: Self-contained component
bx:param name="attributes.data" type="array" required="true";
// Avoid: Reaching into caller scope
// variables.data = caller.variables.someData;
Migration from CFML Custom Tags
BoxLang components provide enhanced functionality over CFML custom tags:
<cf_customTag>
<bx:_customTag>
<cfmodule template="path">
<bx:component template="path">
Template context only
Script and template contexts
attributes
scope
attributes
scope
caller
scope
caller
scope
thisComponent
scope
thisComponent
scope
Limited discovery
Enhanced discovery system
Key Advantages in BoxLang:
Components work seamlessly in both script and template contexts
Enhanced parameter validation with
bx:param
Improved discovery system with multiple lookup paths
Better error handling and debugging support
Last updated
Was this helpful?