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.

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:

<!-- Self-closing -->
<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" {
    SELECT id, name, email FROM users WHERE active = 1
}

// File operations
bx:file action="read" file="/path/to/data.txt" variable="fileContent";

// Conditional logic
bx:if condition="#user.isAdmin#" {
    writeOutput( "Admin content here" );
}

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" {
        SELECT * FROM complex_view WHERE processing_intensive = 1
    }
}

// Example: A PDF module might provide
bx:pdf action="generate" filename="report.pdf" {
    // PDF content here
}

Core and module components are registered with the runtime and don't go through the discovery process. They leverage the core Component Service to achieve this.

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.

Basic Custom Component Structure

Let's start with a simple example:

File: components/greeting.bxm

<!--
Simple greeting component that takes a name attribute
Usage: <bx:greeting name="Alice" />
-->

<div class="greeting-card">
    <h2>Hello, #attributes.name ?: "World"#!</h2>
    <p>Welcome to our application.</p>
</div>

Component Input: The attributes Scope

All data passed to your component is available in the attributes scope:

<!-- File: components/userCard.bxm -->

<!-- Best practice: Parameterize your attributes with validation -->
<bx:param name="attributes.userId" type="string" required="true">
<bx:param name="attributes.name" 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 condition="#attributes.showAvatar#">
        <img src="/avatars/#attributes.userId#.jpg" alt="Avatar" class="avatar" />
    </bx:if>

    <div class="user-info">
        <h3>#attributes.name#</h3>
        <p class="email">#attributes.email#</p>
    </div>
</div>

Script syntax for parameterization:

// In a .bxs component file
bx:param name="attributes.userId" type="string" required="true";
bx:param name="attributes.data" default="#arrayNew()#" type="array";
bx:param name="attributes.maxItems" default="10" type="numeric";

Component Content: The thisTag Scope

When components have start and end tags, BoxLang provides the thisTag scope to manage content and execution:

The thisTag scope contains:

  • executionMode: "start" or "end"

  • hasEndTag: boolean indicating if component has closing tag

  • generatedContent: Content between start and end tags

File: components/boldWrapper.bxm

<!-- Component that wraps content in bold tags -->

<bx:if condition="#thisTag.executionMode IS 'end'#">
    <bx:output><b>#thisTag.generatedContent#</b></bx:output>
    <bx:set thisTag.generatedContent = "">
</bx:if>

Usage:

<bx:boldWrapper>This text will be bold</bx:boldWrapper>
<!-- Outputs: <b>This text will be bold</b> -->

Complex Component with Start/End Logic

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 condition="#thisTag.executionMode IS 'start'#">
    <!-- Opening section markup -->
    <section class="content-section">
        <header class="section-header">
            <h2>#attributes.title#</h2>
            <bx:if condition="#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 condition="#thisTag.executionMode IS 'end'#">
    <!-- Process any nested content -->
    #thisTag.generatedContent#

    <!-- Closing section markup -->
        </div>
    </section>

    <!-- Clear the content so it's not output again -->
    <bx:set thisTag.generatedContent = "">
</bx:if>

Usage:

<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):

{
    "customComponentsDirectory": [
        "${boxlang-home}/global/components"
    ],
    "classPaths": [
        "${boxlang-home}/global/classes"
    ]
}

Application Configuration (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:

  1. .bxm (BoxLang template)

  2. .bxs (BoxLang script)

  3. .cfc (CFML component - for compatibility)

  4. .cfm (CFML template - for compatibility)

Calling Custom Components

Method 1: Using bx:component

Script Syntax:

// Basic call
bx:component template="greeting" name="Alice";

// With body content
bx:component template="userCard" userId="123" name="John Doe" {
    writeOutput( "<p>Additional content here</p>" );
}

// With relative or absolute paths
bx:component template="./components/greeting" name="Alice";
bx:component template="/shared/components/layout" title="My Page";

Template Syntax:

<!-- Basic call -->
<bx:component template="greeting" name="Alice" />

<!-- With body content -->
<bx:component template="userCard" userId="123" name="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" name="John Doe" {
    writeOutput( "<p>Additional content</p>" );
}

Template Syntax:

<!-- Looks for greeting.bxm, greeting.bxs, etc. -->
<bx:greeting name="Alice" />

<bx:userCard userId="123" name="John Doe">
    <p>Additional content</p>
</bx:userCard>

Component Scopes Deep Dive

The attributes Scope

Contains 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.name#</h3>
    <bx:if condition="#attributes.showPrice#">
        <p class="price">#attributes.price# #attributes.currency#</p>
    </bx:if>
</div>

The variables Scope

A 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

Provides access to the calling context (use sparingly):

<!-- Component: debugInfo.bxm -->
<bx:if condition="#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 thisTag Scope

Manages 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 condition="#thisTag.executionMode IS '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 condition="#thisTag.executionMode IS 'end'#">
    #thisTag.generatedContent#
        </div>
    </div>
    <bx:set thisTag.generatedContent = "">
</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 condition="#thisTag.executionMode IS 'start'#">
    <nav id="#attributes.id#" class="#attributes.class#">
        <ul class="menu-list">
</bx:if>

<bx:if condition="#thisTag.executionMode IS 'end'#">
    <!-- Process the nested content to collect menu items -->
    #thisTag.generatedContent#

    <!-- Now render all associated menu items -->
    <bx:if condition="#isDefined( 'thisTag.menuItems' ) AND isArray( thisTag.menuItems )#">
        <bx:loop array="#thisTag.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:set thisTag.generatedContent = "">
</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 condition="#thisTag.executionMode IS '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 condition="#thisTag.executionMode IS 'end'#">
    <!-- Process nested content to collect form fields -->
    #thisTag.generatedContent#

    <!-- Render all associated form fields -->
    <bx:if condition="#isDefined( 'thisTag.formFields' ) AND isArray( thisTag.formFields )#">
        <bx:loop array="#thisTag.formFields#" index="field">
            <div class="form-group field-type-#field.type#">
                <bx:if condition="#len( field.label ?: '' )#">
                    <label for="#field.name#" class="form-label">
                        #field.label#
                        <bx:if condition="#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.name#"
                               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.name#"
                                  id="#field.name#"
                                  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.name#"
                                id="#field.name#"
                                class="form-control #field.class ?: ''#"
                                #field.required ? 'required' : ''#>
                            <bx:if condition="#len( field.placeholder ?: '' )#">
                                <option value="">#field.placeholder#</option>
                            </bx:if>
                            <bx:if condition="#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 condition="#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 condition="#isDefined( 'thisTag.formActions' ) AND isArray( thisTag.formActions )#">
        <div class="form-actions">
            <bx:loop array="#thisTag.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:set thisTag.generatedContent = "">
</bx:if>

Child Components:

formField.bxm

<!-- File: components/formField.bxm -->
<bx:param name="attributes.name" 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">

<!-- For select fields, collect options if this is a container -->
<bx:if condition="#attributes.type EQ 'select' AND thisTag.hasEndTag#">
    <bx:if condition="#thisTag.executionMode IS 'end'#">
        <!-- Process nested options -->
        #thisTag.generatedContent#
        <bx:set thisTag.generatedContent = "">
    </bx:if>
</bx:if>

<!-- 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 name="firstName"
                  type="text"
                  label="First Name"
                  required="true"
                  placeholder="Enter your first name" />

    <bx:formField name="email"
                  type="email"
                  label="Email Address"
                  required="true"
                  helpText="We'll never share your email" />

    <bx:formField name="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 name="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

1. Data Collection Names

The dataCollection attribute specifies the name of the array that will be created in the parent component's thisTag 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 condition="#thisTag.executionMode IS 'start'#">
    <div id="#attributes.id#" class="tab-container">
        <ul class="tab-nav" role="tablist">
</bx:if>

<bx:if condition="#thisTag.executionMode IS 'end'#">
    #thisTag.generatedContent#

    <!-- Render tab navigation -->
    <bx:if condition="#isDefined( 'thisTag.tabs' ) AND isArray( thisTag.tabs )#">
        <bx:loop array="#thisTag.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="#thisTag.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:set thisTag.generatedContent = "">
</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 condition="#thisTag.executionMode IS 'end'#">
    <!-- Capture the tab content -->
    <bx:set variables.tabContent = thisTag.generatedContent />
    <bx:set thisTag.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 condition="#thisTag.executionMode IS '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 condition="#attributes.showSidebar#">
                <aside class="sidebar">
                    <bx:navigation />
                </aside>
            </bx:if>

            <main class="content">
</bx:if>

<bx:if condition="#thisTag.executionMode IS 'end'#">
    #thisTag.generatedContent#
            </main>
        </div>

        <bx:footer />
    </body>
    </html>
    <bx:set thisTag.generatedContent = "">
</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

<!-- 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> -->

2. Handle Execution Modes Properly

<!-- Good: Proper execution mode handling -->
<bx:if condition="#thisTag.executionMode IS 'end'#">
    <div class="wrapper">
        #thisTag.generatedContent#
    </div>
    <bx:set thisTag.generatedContent = "">
</bx:if>

<!-- Avoid: Not checking execution mode (causes double execution) -->
<!-- <div class="wrapper">#thisTag.generatedContent#</div> -->

3. Use Descriptive Component Names

<!-- Good -->
<bx:_userProfileCard userId="123" />
<bx:_productListingGrid products="#variables.products#" />

<!-- Avoid -->
<bx:_card data="123" />
<bx:_list items="#variables.items#" />

4. Document Your 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:
  <bx:userProfileCard userId="123" showEmail="false" theme="dark" />
-->

5. Minimize Use of caller Scope

<!-- 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> -->

Migration from CFML Custom Tags

BoxLang components provide enhanced functionality over CFML custom tags:

CFML Custom Tags
BoxLang Components

<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

thisTag scope

thisTag 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?