Only this pageAll pages
Powered by GitBook
1 of 38

v2.x

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Essentials

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Wire Features

Loading...

Loading...

Loading...

Loading...

Loading...

Template Features

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Integrations

Loading...

Loading...

Loading...

Loading...

Release History

In this section, you will find the release notes for each version we release under this major version. If you are looking for the release notes of previous major versions use the version switcher at the top left of this documentation book.

Getting Started

With CommandBox, you can start building reactive CFML apps in seconds.

Requirements

  • Adobe ColdFusion 2018+ or Lucee 5+

  • ColdBox 6+

Installation

Within the root of your project, run:

box install cbwire

If you want the latest bleeding edge, run:

box install cbwire@be

Layout Setup

You need to add references for wireStyles() and wireScripts() to your layout file.

<!--- ./layouts/Main.cfm --->
<cfoutput>
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>CBWIRE Example</title>
        #wireStyles()#
    </head>
    <body>
        
        <!--- JavasScript references below --->
        #wireScripts()#
    </body>
</html>
</cfoutput>

CBWIRE will not work if you do not add these to your layout.

Inserting Wires

<!--- ./layouts/Main.cfm --->
<body>
    <!--- Insert our task list wire here --->
    #wire( "TaskList" )#
            
    <!--- JavasScript references below --->
    #wireScripts()#
</body>
<!--- ./views/someview.cfm --->
<cfoutput>
    <div>#wire( "TaskList" )#</div>
</cfoutput>

Install .

You can insert anywhere in your layout or ColdBox views using wire( componentName ).

CommandBox
Wires

Computed Properties

Create dynamic properties for your UI Wires.

Computed Properties are dynamic properties and are helpful for deriving values from a database or another persistent store like a cache.

  • They are declared as inline functions using computed.

  • They can return any type of CFML value or object, not just values that can be serialized and parsed by JavaScript like Data Properties.

Defining Properties

component extends="cbwire.models.Component" {

    property name="taskService" inject="taskService@myapp";

    // Computed Properties
    computed = {
        "allTasks": function() {
            return taskService.getAll();
        }
    };
}

Accessing Properties

component extends="cbwire.models.Component" {

    property name="taskService" inject="taskService@myapp";

    // Computed Properties
    computed = {
        "allTasks": function() {
            return taskService.getAll();
        }
    };

    // Action
    function deleteTasks() {
        
        // Notice below we don't invoke the method using computed.allTasks().
        // We instead just use 'computed.allTasks'.
        // At this point, cbwire has rendered our computed properties
        // and overwritten the references with their result.
        if ( arrayLen( computed.allTasks ) {
            taskService.deleteAll();
        }

    }
}

Getters

Getters are automatically created based on the property's name.

In the example below, you to access the property using this.getAllTasks(). You can use this as an alternative to using computed.

component extends="cbwire.models.Component" {

    property name="taskService" inject="taskService@myapp";

    // Computed Properties
    computed = {
        "allTasks": function() {
            return taskService.getAll();
        }
    };

    // Action
    function deleteTasks() {
        if ( arrayLen( this.getAllTasks() ) {
            taskService.deleteAll();
        }
    }
}

Templates

<cfoutput>
<div>
    <ul>
        <cfloop array="#args.allTasks#" index="task">
            <li>#task#</li>    
        </cfloop>
    </ul>
</div>
</cfoutput>

Configuration

Alter CBWIRE's behavior with these nifty configuration options.

ColdBox.cfc

You can alter any default behavior by overriding settings in your config/ColdBox.cfc file.

// File: ./config/ColdBox.cfc
component{
    function configure() {
        moduleSettings = {
            cbwire = {
                "enableTurbo": false,
                "maxUploadSeconds": 5 * 60, // 5 minutes
                "throwOnMissingSetterMethod" : false,
                "trimStringValues": false,
                "wiresLocation": "myWires",
                "useComputedPropertiesProxy": false
            }
        };
     }
}

enableTurbo

maxUploadSeconds

The maximum amount of time allowed for uploads to complete.

throwOnMissingSetter

trimStringValues

When set to true, any data properties that contain strings will be automatically trimmed. Great for form inputs. Defaults to false.

wiresLocation

useComputedPropertiesProxy

Wires

Wires are reactive UI elements you create. They include a powerful feature-set provided by CBWIRE.

Wires primarily consist of the following:

Basic Example

// File: wires/TaskList.cfc
component extends="cbwire.models.Component" {

    // Data properties
    data = {
        "task": "",
        "tasks": []
    };
    
    // Computed properties
    computed = {
        "counter": function() {
            return arrayLen( data.tasks );
        }
    };
    
    // Listeners
    listeners = {
        "taskAdded": "sendEmail"
    };
    
    // Action
    function addTask(){
        data.tasks.append( data.task );
        this.emit( "taskAdded", data.task );
    }
    
}
<!--- /views/wires/tasklist.cfm --->
<cfoutput>
<div>
    <div>Number of tasks: #args.computed.counter()#</div>
    <div><input wire:model="task"></div>
    <div><button wire:click="addTask">Add Task</button></div>
</div>
</cfoutput>

Scaffolding

From our CommandBox shell, type:

install commandbox-cbwire

Let's create a Counter Wire that we will use to list our favorite movies.

cbwire create Counter
// Created /wires/Counter.cfc
// Created /views/wires/counter.cfm
// Created /tests/specs/integration/wires/CounterTest.cfc

You can provide named arguments as well.

cbwire create name=Counter views=false
// Created /wires/Counter.cfc
// Created /tests/specs/integration/wires/CounterTest.cfc

Data Properties

Define reactive properties for your UI Wires with just a few lines of code.

Defining Properties

component extends="cbwire.models.Component"{
    data = {
        "task": ""
    };
}

When data properties are mutated, the UI will update also.

Accessing Properties

Data Variable

You can reference a property using data.[propertyName].

component extends="cbwire.models.Component"{

    property name="taskService" inject="TaskService@myapp";
    
    // Data properties
    data = {
        "task": ""
    };
    
    // Action
    function addTask(){
        taskService.create( data.task );
    }
}

Getters

Getter methods are automatically available for your properties based on the property's name. You to access the property within your Wire using this.get[propertyName]().

component extends="cbwire.models.Component"{
    
    property name="taskService" inject="TaskService@myapp";

    // Data properties
    data = {
        "task": ""
    };
    
    // Action
    function addTask(){
        taskService.create( this.getTask() );
    }
}

You can use this as an alternative to using data.

Templates

You can reference data properties within your Wire's template using args.[propertyName].

<cfoutput>
<div>
    <h1>Task</h1>
    <div>#args.task#</div>
</div>
</cfoutput>

Resetting Properties

You can reset properties back to their original value using reset().

component extends="cbwire.models.Component"{

    // Data properties
    data = {
        "task": "Some task here"
    };

    // Action
    function resetForm() {
        data.task = "Another value";
        reset( "task" ); // Reverts data.task to 'Some task here'
    }

}

There are several ways to use reset.

function someAction() {
    reset( "task" ); // resets 'task' data property
    reset( [ "task", "anotherprop" ] ); // reset multiple properties at once
    reset(); // resets all properties
}

Security

Your component's private variables scope will hold your data property definitions and current values, but it's necessary to be cautious about what you store.

You should NEVER store sensitive data ( such as passwords, and SSNs ) that you wouldn't want your application's users to see.

What's New With 2.1

11/26/2022

Added

Fixed

Wire Lifecycle

Everything that has a beginning has an end. - The Oracle, Matrix

Order of Operations

  1. onMount()

For background AJAX requests, lifecycle methods are executed in this order.

  1. onHydrate[DataProperty]()

  2. onHydrate()

onMount

Runs only once when the Wire is initially wired.

This does not fire during subsequent renderings for the Wire.

component extends="cbwire.models.Component" {

    data = {
        "taskId": 0
    };

    function onMount( event, rc, prc, parameters ){
        data.taskId = event.getValue( "taskId" );
    }

}

onMount() replaces what was formerly mount(). The mount() method is deprecated and will be removed in future major releases of CBWIRE.

onHydrate

component extends="cbwire.models.Component" {

    data = {
        "hydrated": false
    };

    function clicked() {}

    function onHydrate( data ) {
        // Note that computed properties are not rendered yet
        data.hydrated = true;
    }

    function onRender( args ) {
        return "
            <div>
                <div>Hydrated: #args.hydrated#</div>
                <button wire:click='clicked'>Click me</button>
            </div>
        ";
    }
}

onHydrate[ DataProperty ]

component extends="cbwire.models.Component" {

    data = {
        "count": 1
    };

    function onHydrateCount( data ) {
        // Note that computed properties are not rendered yet
        data.count += 1;
    }  
}

onRender

component extends="cbwire.models.Component" {

    data = {
        "rendered": true
    };
    
    computed = {
        "currentDate": function() {
            return now();
        }
    }

    function onRender( args ) {
        return "
            <div>
                <div>Rendered: #args.rendered# at #args.currentDate#</div>
            </div>
        ";
    }
}

Examples

See CBWIRE in action!

Git Repository

In addition to the examples above, we have a GitHub repo that contains more examples of CBWIRE. Each example includes both the code to generate the example and a section where you can see the results in real-time.

Templates

Your Wire's HTML. Simple as that. Honestly.

Implicit Path

If you haven't specified your template file name inside your Wire using template, a template will be implicitly loaded based on the Wire's file name.

component extends="cbwire.models.Component" {
    // No template defined.
}

For the example above, the template ./views/wires/tasklist.cfm will be rendered.

File names should be lowercase when using Implicit Lookups. This is to avoid issues on case-sensitive file systems.

Explicit Path

You can specify the path to your Template using template.

component extends="cbwire.models.Component" {
    template = "wires/tasklist"; // This will look for ./views/wires/tasklist.cfm

    // template = "path/to/tasklist"; <--- Can use folders
    
    // template = "tasklist.cfm"; <--- Can use .cfm if you like
}

In CBWIRE 1.x, you would define the template path using view but this has been deprecated and template should be used going forward.

onRender

Prevent Rendering

component extends="cbwire.model.Component"{
    // Action
    function updateSession(){
        // prevent UI updating
        noRender();
    }
}

Outer Element

Be sure your template includes a single outer element such as<div>.

<!--- GOOD --->
<cfoutput>
    <div>
        <button wire:click="addTask">Add Task</button>
    </div>
</cfoutput>

<!--- BAD --->
<cfoutput>
    <div>
        <button wire:click="addTask">Add Task</button>
    </div>
    <div>
        <h1>Tasks</h1>
    </div>
</cfoutput>

If an outer element is not detected, an OuterElementNotFoundexception is thrown.

You can use something other than <div></div>.

Validation

Defining Constraints

You can define constraints within your wires using this.constraints.

component extends="cbwire.models.Component"{

    this.constraints = {
        "task": { required: true }
    };
    
    // Data Properties
    data = {
        "task": ""
    };
}

Validating

validate

Returns a ValidateResult object.

component extends="cbwire.models.Component"{
    // Action
    function addTask() {
        var result = validate(); // ValidateResult object
        
        if ( !result.hasErrors() ) {
            queryExecute( ... );
        }
    }
}

validateOrFail

component extends="cbwire.models.Component"{
    // Action
    function addTask() {
        validateOrFail();

        queryExecute( ... );        
    }
}

With validateOrFail(),the error is gracefully caught and any further processing of the invoked action is prevented. The XHR response and re-rendered template are still returned. The actual errors themselves are available to the template using args.validation.

If you need more granular control over the validation response, use validate() instead.

Validation Manager

You can get a new ValidationManager object to work with, just call getValidationManager();

component extends="cbwire.models.Component"{
    // Action
    function addTask() {
        var data = { "email": "cbwire@rocks.com" };

        var validationManager = getValidationManager();
        
        var result = validationManager.validate(
            target=data,
            constraints={
                "email": { required: true, type: "email" }
            }
        );
    }
}

Displaying Errors

<cfoutput>
<div>
    <input wire:model="task" type="text">
    <button wire:click="addTask">Add</button>
    
    <cfif args.validation.hasErrors( "task" )>
        <cfloop array="#args.validation.getAllErrors( "task" )#" index="error">
            <div>#error#</div>
        </cfloop>
    </cfif>
</div>
</cfoutput>

If you are on CBWIRE version 2.3.x and have enabled the configuration property useComputedPropertiesProxy, then you will need to follow instead as this fundamentally changes how computed properties are referenced and accessed.

Computed Properties are similar to with some key differences:

See the page for details on when Computed Properties are executed.

Computed Properties are cached for the lifetime of the request. Meaning, if you reference your Computed Property three times in your or from within a wire , it will only execute once.

You can define Computed Properties on your using computed. Each Computed Property is invoked only once during a single request cycle.

You can access your Computed Properties from within your using computed.[propertyName].

You can access Computed Properties in your via the args scope.

Overriding the module settings is optional. All settings come with a default that is used if no overrides are provided. See in the ColdBox documentation for additional reference.

Set as true to enable , which causes any clicked links or form submissions to be performed in the background via AJAX and updates the DOM without reloading the page. Great when developing single-page, VueJS-like applications. Defaults to false.

Set as true to throw a WireSetterNotFound exception if the incoming wire request tries to update a property without a setter on our . Otherwise, missing setters are ignored. Defaults to false.

The relative folder path where are stored. Defaults to 'wires'.

When set to true, Computed Properties will be proxied. See .

Reactive properties that we can easily change.

Properties that are computed every time our template is rendered.

Methods that make state changes to our data properties.

Listen for emitted events within Wires or client-side JavaScript.

HTML to render our Wire.

You can scaffold Wires quickly using the module.

Similar to Vue.js and other frontend JavaScript frameworks, you can define reactive properties on your .

You can define and initialize properties on your using data.

Data Properties are parsed and tracked by Livewire and JavaScript, therefore only data types that are castable to JavaScript can be stored in Data Properties (strings, numeric, arrays, structs, or booleans). If you are needing more complex data types for your templates, use instead.

The values of your data properties are included with each XHR response and Livewire uses these values to determine what should be updated in the .

Ability to output component template direct from onRender() method instead of defining a .cfm template in wire/views.

Computed properties that do not return a value result in the error 'variable [VALUE] doesn't exist

Nested components are causing the template rendering only to render the last nested template

Struct values are not being passed to the template and are instead being replaced with an empty string

CBWIRE doesn't work when the ColdBox app is in a subdirectory

Getting errors when rendering component templates in the latest version of ColdBox

Nested components are not rendering

When a is initially loaded (before any background AJAX requests are performed), the Lifecycle Methods are executed in the following order.

Render

onRender() or implicit rendering

Render

Execute

onRender() or implicit rendering

Runs on subsequent requests, after the Wire is hydrated, but before are rendered, before an is performed, or before onRender() is called.

Runs on subsequent requests, after a specific is hydrated, but before are rendered, before an is performed, or before onRender() is called.

Runs during the rendering of a . If onRender() is defined on the Wire, CBWIRE will use what is returned as the component's template, otherwise it will perform a lookup for a view template. The args parameter is provided which contains both the and any rendered .

require a template that contains the HTML to be rendered.

By default, Templates exist within your project under ./views/wires/[templateName].cfm. You can override the default template location with the configuration setting templatesLocation. See

Instead of creating a .cfm template, you can define an onRender() method on your .

See the page for additional details.

Within your , you can call the method noRender() to prevent your template from re-rendering. This can reduce the returned payload size for actions that do not change the UI.

include powerful validations that you can use to validate your .

Validations are provided by .

can validate against the defined constraints using validate() or validateOrFail().

Silently fails and prevents further processing of the current .

can access the ValidationResults object using args.validation. This includes helpful methods you can use for displaying error messages.

this guide here
Data Properties
Wires
Actions
Overriding Module Settings
Turbo
Wire
Wires
Computed Properties (Proxied)
Data Properties
Computed Properties
Actions
Events and Listeners
Templates
commandbox-cbwire
Wires
Wires
Computed Properties
DOM
CBWIRE-111
CBWIRE-119
CBWIRE-118
CBWIRE-117
CBWIRE-116
CBWIRE-115
CBWIRE-96
Wire
Computed Properties
Template
Computed Properties
Actions
Template
Computed Properties
Action
Data Property
Computed Properties
Action
Wire
Data Properties
Computed Properties
https://github.com/grantcopley/cbwire-examples
Wires
Configuration
Wire
Actions
Wires
Data Properties
cbValidation
Actions
Action
Templates
Wire Lifecycle
Wire Lifecycle
Action
Template
Template

What's New With 2.0

08/30/2022

Added

CBWIRE-24: File Uploads

CBWIRE-55: Ability to write unit tests for components

CBWIRE-59: Ability to override the default 'wires' folder location

CBWIRE-60: noRender() method to prevent template rendering during actions

CBWIRE-61: Implement dirty property tracking

CBWIRE-65: Invoke computed properties during template rendering and only execute once per request

CBWIRE-70: Ability to specify a 'moduleRootURI' setting to change the URI path for cbwire

CBWIRE-71: Upgrade Livewire JS to v2.10.6

CBWIRE-81: Option to specify the component's template path using this.template instead of defining renderIt() method.

CBWIRE-82: Disable browser caching on XHR responses

CBWIRE-83: Reduce payload bloat by removing unnecessary data in XHR requests and responses

CBWIRE-84: Move internal methods to a separate Engine object to avoid collisions with user-defined methods

CBWIRE-85: Reject incoming XHR request if 'X-Livewire' HTTP Header is not present

CBWIRE-86: Specify null values for data properties

CBWIRE-87: Dependency injection capabilities to cbwire components

CBWIRE-90: Update to match Livewire's current incoming and outgoing HTTP responses

CBWIRE-91: Ability to use Turbo to create Single-page Applications ( SPAs )

Fixed

CBWIRE-73: Calling reset( "someProperty" ) throws error

CBWIRE-74: Browser back history doesn't work

CBWIRE-80: On subsequent renderings of components, it's changing the unique id and causing DOM diff issues

CBWIRE-88: Livewire expects params to be an array

CBWIRE-89: Not passing parameters when calling update methods

Computed Properties ( Proxied )

Create dynamic properties for your UI Wires.

In CBWIRE 2.3.5, we introduced the ability to proxy Computed Properties, which offers several advantages, including:

  • Computed Properties are passed around as closures and are only rendered when called.

  • The ability to invoke Computed Properties anywhere they are needed (both Actions and Templates).

  • The ability to cache Computed Property results to enhance performance.

Computed Properties are dynamic properties and help derive values from a database or another persistent store like a cache.

  • They are declared as inline functions using computed.

  • They can return any CFML value or object.

Defining Properties

component extends="cbwire.models.Component" {

    property name="taskService" inject="taskService@myapp";

    // Computed Properties
    computed = {
        "allTasks": function() {
            return taskService.getAll();
        }
    };
}

Accessing From Actions

component extends="cbwire.models.Component" {

    // Computed Properties
    computed = {
        "allTasks": function() {
            // return something here
        }
    };

    // Action
    function deleteTasks() {      

        if ( arrayLen( computed.allTasks() ) {
            taskService.deleteAll();
        }

    }
}

Accessing From Templates

<cfoutput>
<div>
    <ul>
        <cfloop array="#args.computed.allTasks()#" index="task">
            <li>#task#</li>    
        </cfloop>
    </ul>
</div>
</cfoutput>

Actions

The magic sauce. Actions allow you to easily listen to page interactions and call a method on your Wires.

Here is a basic example of how to use it:

component extends="cbwire.model.Component"{
  
    // Actions
    function addTask(){
      queryExecute( ... );
    }
}
<!--- Template --->
<div>
    <button wire:click="addTask">Add Task</button>
</div>

Invoking Actions

Some examples of events you can listen for include:

Event
Directive

click

wire:click

keydown

wire:keydown

submit

wire:submit

Here are a few examples of each in HTML:

<a href="" wire:click.prevent="doSomething">Do Something</a>

<button wire:click="doSomething">Do Something</button>

<input wire:keydown.enter="doSomething">

<form wire:submit.prevent="save">
    <button>Save</button>
</form>

You can listen for any browser events on elements by using the wire:[event] directive, where [event] is the event's name. For example, to listen for a "foo" event on a button element, you would use the following code:

<button wire:foo="someAction">

Passing Parameters

You can pass parameters to your actions, such as here using addTask('Some Task').

<button wire:click="addTask('Some Task')">Add Task</button>
// Action
function addTask( taskName ){

}

Return Values

Actions shouldn't return any value. Return values are ignored.

Accessing Data Properties

You can access data properties inside your actions directly using data.

    data = {
        "task": ""
    };

    // Actions
    function clearTasks(){
        data.task = "";
    }

Accessing Computed Properties

You can access Computed Properties inside your actions directly using computed.

    // Computed Properties
    computed = {
        "tasks": function() {
            return queryExecute( "
                select *
                from tasks
            " );

        }
    };

    // Actions
    function deleteTasks(){
        if ( computed.tasks.recordCount ) {
            // query to delete the tasks...    
        } 
    }

Notice that our Computed Property above is defined as a closure function, but once our deleteTasks() action is called, the computed property has already been rendered.

Magic Actions

In CBWIRE, there are some "magic" actions that are usually prefixed with a "$" symbol:

Function
Description

$refresh

Will re-render the component without firing any action

$set( 'dataproperty', value )

Shortcut to update the value of a property

$toggle( 'dataproperty' )

Shortcut to toggle boolean properties off and on

Consider the example below.

<div>
    #args.message#
    <button wire:click="setMessageToHello">Say Hi</button>
</div>

You can instead call $set and avoid the need to create an Action named setMessageToHello.

<div>
    #args.message#
    <button wire:click="$set( 'message', 'Hello' )">Say Hi</button>
</div>

It can also be used in the backend when listening for an event. For example, if you have one component that emits an event like this:

function someAction() {
    emit( "some-event" );
}

Then in another component you can use a magic action for example $refresh() instead of having to point the listener to a method:

component {
    listeners = {
        "some-event": "$refresh"
    };
}

JavaScript

Seamlessly connect between the front-end and your server back-end using JavaScript and CFML.

Lifecycle Hooks

CBWIRE gives you the opportunity to execute JavaScript during various events.

Hook
Description

component.initialized

element.initialized

Called when Livewire initializes an individual element

element.updating

Called before Livewire updates an element during its DOM-diffing cycle after a network roundtrip

element.updated

Called after Livewire updates an element during its DOM-diffing cycle after a network roundtrip

element.removed

Called after Livewire removes an element during its DOM-diffing cycle

message.sent

Called when a Livewire update triggers a message sent to the server via AJAX

message.failed

Called if the message send fails for some reason

message.received

Called when a message has finished its roudtrip, but before Livewire updates the DOM

message.processed

Called after Livewire processes all side effects (including DOM-diffing) from a message

<script>
    document.addEventListener("DOMContentLoaded", () => {
        cbwire.hook('component.initialized', (wire) => {})
        cbwire.hook('element.initialized', (el, wire) => {})
        cbwire.hook('element.updating', (fromEl, toEl, wire) => {})
        cbwire.hook('element.updated', (el, wire) => {})
        cbwire.hook('element.removed', (el, wire) => {})
        cbwire.hook('message.sent', (message, wire) => {})
        cbwire.hook('message.failed', (message, wire) => {})
        cbwire.hook('message.received', (message, wire) => {})
        cbwire.hook('message.processed', (message, wire) => {})
    });
</script>

Interacting With Wires

<script>
    document.addEventListener("livewire:load", function() {
        var thisWire = cbwire.find('#args._id#'); // args._id contains the id of our wire
    
        var count = thisWire.count; // gets the value of a data property called 'count'   
        
        thisWire.count = 5; // updates the values of a data property
    
        thisWire.increment(); // calls the increment action on our wire
        
        thisWire.addTask( 'someTask' ); // calls the addTask action and passes parameters 
        
        thisWire.call( 'increment' ); // same as increment call above 
    
        // On someEvent, console log
        thisWire.on( 'someEvent', function() {
            console.log('Got someEvent');
        } );  
        
        thisWire.emit('someEvent', 'foo', 'bar'); // emits someEvent and pass parameters
    } );
</script>

Events & Listeners

You can emit events from both your Wires and JavaScript. Superb!

Emitting Events

Actions

Using the emit method:

function someAction() {
    emit( "taskAdded" );
}

You can provide arguments to all listeners by passing an array as the second argument.

function someAction() {
    emit( "taskAdded", [
        "some task",
        now()
    ] );
}

When emitting events, arguments must be passed as an array to ensure proper positioning both within CFML and JavaScript.

Templates

Using the $emit() method:

<button wire:click="$emit( 'taskAdded' )">

JavaScript

Using the global cbwire.emit():

<script>
    cbwire.emit( 'taskAdded' );
</script>

Listeners

You can register event listeners on a Wire by defining listeners.

component extends="cbwire.models.Component"{
    
    listeners = {
        "taskAdded": "clearCache"
    };

    function clearCache(){}
}

The clearCache method will be invoked when a taskAdded event is emitted.

JavaScript keys are case-sensitive. You can preserve the key casing in CFML by surrounding your listener names in quotations.

JavaScript

<script>
    cbwire.on( 'taskAdded', task => {
        console.log( 'A task was added. ');
    })
</script>

Testing

Front-end testing of your UI Wires using a beautiful test API.

Test Example

component extends="cbwire.models.BaseWireTest" {

    function run(){

        describe( "TaskList.cfc", function(){
	
	    it( "calling 'clearTasks' removes the tasks", function() {
        
                wire( "TaskList" )
                    // Sets our data properties
                    .data( "tasks", [ "task1" ] )
                    // Verifies output in our template rendering
                    .see( "<li>task 1</li>" )
                    // Runs the 'clearTasks' action
                    .call( "clearTasks" )
                    // Verifies updated template rendering
		    .dontSee( "<li>task 1</li>" );
		} );
	
	} );
    }
}

Test Methods

data

wire( "TaskList" )
    .data( "tasks", [ "task1", "task2" ] ) // one property at a time
    .data( { "tasks" : [ "task1", "task2" ] } ); // struct of properties

computed

wire( "TaskList" )
    .computed( "count", function() { return 3; } ) // one property at a time
    .computed( { "count" : function() { return 3; } } ); // struct of properties

toggle

wire( "TaskList" ).toggle( "showModal" );

call

wire( "TaskList" ).call( "clearTasks" );
wire( "TaskList" ).call( "clearTasks", [ param1, param2 ] );

emit

wire( "TaskList" ).emit( "addedTask" );
wire( "TaskList" ).emit( "addedTask", [ param1, param2 ] );

see

wire( "TaskList" ).see( "<h1>My Tasks</h1>" );

dontSee

wire( "TaskList" ).dontSee( "<h1>Someone Else's Tasks</h1> ");

seeData

wire( "TaskList" ).seeData( "tasksRemoved", true );

dontSeeData

wire( "TaskList" ).dontSeeData( "tasksRemoved", true );

File Uploads

Basic File Upload

CBWIRE makes uploading and storing files easy.

Here's an example of a simple Wire that handles uploading a photo:

component extends="cbwire.models.Component" {

    data = {
        "photo": "",
        "error": ""
    };
    
    function save() {
        // 'photo' is now an instance of FileUpload@cbwire
        if ( data.photo.getSize() > "100000" ) {
            data.error = "Photo upload too big!";
        } else {
            // Store our file locally
            fileWrite( expandPath( "./somewhere.jpg" ), data.photo.get() );
            
            // Delete our file from CBWIRE's temporary storage and reset 'photo' data property
            data.photo.destroy();
        }
    }
}
<form wire:submit.prevent="save">
    File: <input type="file" wire:model="photo">
    <div><button type="submit">Save</button></div>
    
    <!--- Show thumbnail --->
    <cfif isObject( args.photo ) and args.photo.isImage()>
        <div><img src="#args.photo.getPreviewURL()#"></div>
    </cfif>
    
    <cfif len( args.error ) >
        <div class="error">#args.error#</div>
    </cfif>
</form>

Handling file inputs is no different than handling any other input type with CBWIRE. Add wire:model to the <input> tag and CBWIRE will take care of the rest.

There are several things happening under the hood to make file uploads work:

  1. CBWIRE makes an initial request to the server to get a temporary "signed" upload URL.

  2. Once the URL is received, JavaScript then does the actual "upload" to the signed URL, storing the upload in a temporary directory designated by CBWIRE and returning the new temporary file's unique hash ID.

  3. Once the file is uploaded, and the unique hash ID is generated, CBWIRE makes a final request to the server, telling it to "set" the desired data property to the upload file as an instance of FileUpload ( see below ).

  4. Now the data property (in this case photo ) is set to an instance of FileUpload@cbwire and is ready to be stored or validated at any point.

FileUpload Object

CBWIRE will automatically upload your files and set your associated data property to an instance of FileUpload@cbwire. Here are some of the methods you can use that can get quite helpful.

Method
Description

getComp()

Returns an instance of your current Wire.

getParams()

Returns any params that are passed in, including the data property name.

get()

Returns the contents of the file uploaded.

getBase64()

Returns base64 encoded string of the file.

getBase64Src()

Returns a base64 src string that can be used with <img tag>. It is recommended to use this carefully because with larger objects, this can slow down CBWIRE response times.

getTemporaryStoragePath()

Returns the temporary storage path where the file is stored by CBWIRE.

getMetaPath()

Returns the file path to meta information of the uploaded file. You can find additional details here like the name of the file when it was uploaded, the client file extension, etc.

getMeta()

Returns all captured meta information on the file upload.

getSize()

Returns the size of the file upload.

getMimeType()

Returns the mime type of the file upload.

isImage()

Returns true if this is an image.

getPreviewURL()

Provides a URL to preview the file uploaded. It only works with image uploads.

destroy()

Deletes the temporary storage and metadata of the file upload. It's essential to use this after storing the file upload in permanent storage.

Loading Indicators

You can display a loading indicator scoped to the file input during upload like so:

<input type="file" wire:model="photo">
 
<div wire:loading wire:target="photo">Uploading...</div>

The "Uploading..." message will be shown as the file is uploading and then hidden when the upload is finished.

Dependency Injection

Just as with ColdBox, you can inject your dependencies using WireBox.

WireBox

component extends="cbwire.models.Component" {

    // Property injection
    property name="storage" inject="cbfs:disks:temp";

    // Setter injection
    function setStorage() inject="cbfs:disks:temp" {};

    // Actions
    function someAction() {
        // Use the storage object
        storage.create( "log.txt", "CBWIRE rocks!" );
        // Use getInstance() to get objects.
        var defaultStorage = getInstance( "cbfs:disks:default" ); 
    }

}

onDIComplete

If you want to act immediately after dependency injection has been completed, you can define an onDIComplete method.

component extends="cbwire.models.Component" {

    property name="storage" inject="cbfs:disks:temp";

    function onDIComplete(){
        storage.create( "log.txt", "Dependency Injection Complete!" );
    }

}

Offline State

Toggle UI elements when the user is offline or online. Groovy!

You can use wire:offline to display elements when Livewire detects that the user is offline.

Use .remove to specify classes you want to be removed when offline.

Polling

Poll for state changes based on a specified interval without page refreshes. Hot dog!

You can add a wire:poll directive to your elements to poll for changes using a set interval. The default interval is set to poll every 2 seconds.

You can append a different interval time to your directive as well.

Polling for changes over AJAX can be a resonable alternative to strategies such as Pusher or WebSockets.

Method Invocation

If you would like to invoke a method during each poll interval, you can do so by specifying a method name.

Cancel Polling

If you want stop polling, you can simply no longer render the HTML element that has the wire:poll directive.

Redirecting

Similar to ColdBox, you can relocate users using relocate.

relocate

Redirects a user to a different URI, URL, or event.

CBWIRE's relocate() method signature is nearly identical to ColdBox's internal relocate() method, with the exception that status codes cannot be set. Otherwise, most of the arguments that ColdBox accepts can be used.

Directives

CBWIRE's powerful directives allow you to listen for client side events, invoke actions, bind to data properties, and more.

Core Directives

wire:key

Define a key name foo for the element. This provides a reference point for the Livewire DOM diffing system. Useful for adding/removing elements and keeping track of lists.

wire:click

wire:click.prefetch

wire:submit

Listeners for a submit event on a form.

wire:keydown

wire:keydown.enter

wire:foo

You can listen to any JS event, not just those defined by Livewire.

wire:model

wire:model.debounce

The same as wire:model="foo" except that all input events to the element will be debounced for the specified duration. Defaults to 150 milliseconds. Useful for reducing XHR background requests during user input. You can set the milliseconds value to any numeric value.

wire:model.defer

wire:model.lazy

If your input field does not require immediate updating ( such as for instant form validation), we highly recommended you used the lazy modifier to reduce the number of XHR requests .

wire:poll

wire:init

wire:loading

Hides the HTML element by default and makes it visible when XHR requests are performed.

wire:loading.class

Adds the foo class to the HTML element while XHR requests are in transit.

wire:loading.class.remove

Removes the foo class from the HTML element while XHR requests are in transit.

wire:loading.attr

Adds the disabled="true" attribute while XHR requests are in transit.

wire:dirty

Hides the HTML element by default and makes it visible when the element's state has changed since the latest XHR request.

wire:dirty.class

Adds the foo class to the HTML element when the element's state has changed since the latest XHR request.

wire:dirty.class.remove

Removes the foo class from the HTML element when the element's state has changed since the latest XHR request.

wire:dirty.attr

Adds the disabled="true" attribute when the element's state has changed since the latest XHR request. Used in addition to wire:target.

wire:target

wire:ignore

Instructs Livewire to not update the element or any child elements when updating the DOM. Useful when using third-party JavaScript libraries.

wire:ignore.self

Instructs Livewire to not update the element but DOES allow updates to any child elements when updating the DOM.

wire:offline

Event Modifiers

CBWIRE Directives sometimes offer "modifiers" to add extra functionality to an event. Here are the available modifiers that can be used with any event.

Keydown Modifiers

Here is a quick list of some common ones you may need:

Loading States

Help website users during wait times using Loading States.

Loading States can make your apps feel much more responsive and user-friendly.

Toggling Elements

Let's look at an action that takes too long.

We can annotate our <div> with wire:loading to display Adding Task while the checkout action is running.

Toggling Attributes

Targeting Specific Actions

You can target a specific action so that the element is only visible is a specific action is loading using wire:target.

Delaying

If you want to avoid flickering because loading is very fast, you can add a .delay modifier, and it will only show up if loading takes longer than 200ms.

If you wish, you can customize the delay duration with the following modifiers:

Display Property

Loading State elements are set with a CSS property of display: inline-block; by default. You can override this behavior by using various directive modifiers.

Hiding

If you would like to display an element except during a loading state, you can apply the wire:loading.remove directive.

Prefetching

Prefetch state changes when the user mouses over an HTML element. Fantastico!

In the example here, the togglePreview action will be prefetched and invoked when the user mouses over the button. The results of the fetch are not displayed until the user clicks the 'Show Preview' button.

Prefetching works well for actions that do not perform any side effects, such as mutating session data or writing to a database. If the action you are "pre-fetching" does have side effects, you may encounter unpredictable results.

What's New With 2.2

01/09/2022

Added

Changed

Fixed

Introduction

CBWIRE is a ColdBox module that makes building modern, reactive CFML apps a breeze without the need for JavaScript frameworks such as Vue or React, and without the hassle of creating unnecessary APIs.

But what if you could have the best of both worlds: the power of Vue and React with the simplicity of CFML? Impossible, you say? Think again!

Introducing CBWIRE: Power up your CFML and make your app dreams a reality!

Let's create a counter...

Next, add wireStyles() and wireScripts() to our layout. This is required for CBWIRE to do it's magic.

Let's also insert a counter element into the page using wire( "Counter" ).

Refresh the page. You now have a reactive counter that increments when you click the plus button without any page refreshing!

What!? How?

  1. CBWIRE renders our Counter Wire template. Our counter value defaults to 0.

  2. When a user clicks the plus button, CBWIRE detects the event and makes an XHR request to the server.

  3. CBWIRE picks up the XHR request and invokes the increment action.

  4. The increment method updates the counter state by 1.

  5. CBWIRE re-renders the template and returns the updated HTML in the XHR response

  6. CBWIRE detects any state changes and uses Livewire to mutate the DOM.

Awesome, right?

  • We built a reactive counter with no page refreshing.

  • We didn't write any JavaScript.

  • We didn't use webpack or mess with JavaScript compilation.

CBWIRE is transforming the way we build CFML applications, and we think you're going to love it also!

Credits

Project Support

Resources

You must be on CBWIRE version 2.3.5 or later and enable the setting useComputedPropertiesProxy to follow this guide.

Otherwise, please follow instead.

Computed Properties are similar to with some key differences:

Define Computed Properties in your using computed.

You can access your Computed Properties from within your using computed.[propertyName]().

You can access Computed Properties in your using args.computed.[computedProperty]().

Actions provide an effortless way to track page interactions and invoke methods on your , resulting in a re-render of the Wire's .

You can listen for browser events and invoke actions using a selection of various . The directives follow the format: wire:[browser event]=[action].

The parameter is then passed through to your via function arguments.

Called when a has been initialized on the page by Livewire

You can interact with your , calling , setting , and more, using cbwire.find from within your . Once you have a reference to the Wire, you can interact with it using the methods below.

You can emit events from , , and JavaScript.

Listening to events that you emit in your can be done using cbwire.on().

You can easily test your by extending cbwire.models.BaseWireTest and using our fluent testing API in your specs.

You can invoke your for testing by using wire().

Set a to the specified value.

Set a to the specified closure.

Toggles a between true and false.

Calls an . Optional array of parameters can be provided.

Emits an and fires any that are defined on the . Optional array of parameters can be provided, which will be passed on to listeners.

Verifies a value can be found in the current rendering. Otherwise, test fails.

Verifies a value is not found in the current rendering. Otherwise, test fails.

Verifies a matches a specified value. Otherwise, test fails.

Verifies a does not match a specified value. Otherwise, test fails.

This works with the entire .

have available for injecting dependencies such as service objects, session storage, you name it!

You can also append .class to your wire:offline and specify a CSS class to toggle when the user is offline.

You can redirect users in your using relocate().

The following Directives can be added to your and instruct CBWIRE how and when to update the .

Listens for a click event and invokes the foo .

You will want to add the .prevent to ensure that the browser doesn't follow the link.

Listens for a mouseEnter event and then prefetches the result of the foo . If the element is then clicked, it will swap in the prefetched result without any extra request. If it's not clicked, the cached results will be thrown away.

You will want to add the .prevent to ensure that the browser doesn't submit the form.

Listens for a keyDown event and invokes the foo .

Listens for a keyDown event when the user hits enter and invokes the foo .

Listens for a foo event and invokes the bar .

Provided you have data[ "foo" ] defined in your , this creates a one-to-one model binding. Any time the element is updated, the value is synchronized.

Creates a one-to-one model binding with data[ "foo" ] in your but defers any XHR updates until an is performed. Useful for reducing XHR requests.

Creates a one-to-one model binding with data[ "foo" ] in your but will not perform an XHR updates until an onBlur event is emitted. Useful for reducing XHR requests.

Performs an XHR request to re-render the elements based on a set interval. An interval can be specified in both seconds or milliseconds. You can also specify an that you want to invoke.

Invokes the foo on your immediately after it's rendered on the page.

Provides scoping for wire:loading and wire:dirty references, scoped to a specific .

See

Modifier

To listen for specific keys on keydown events, you can provide the name of the key as a modifier. You can use any valid key names exposed via as modifiers by converting them to kebab-case.

Native Browser Event
Livewire Modifier

In the above example, the foo will only be called if event.key is equal to 'PageDown'.

may involve a long-running process (such as completing a cart checkout) and HTML may not re-render instantly. You can help your users during the wait using Loading States. Loading States allow you to show/hide elements, add/remove classes, or toggle HTML attributes until the server responds.

After the action completes, the Adding Task... output will disappear.

You can prefetch an results on mouseOver using the .prefetch modifier.

: Implement lifecycle hook onHydrate().

: Implement lifecycle hook onHydrate[Property]().

: Implement an automatic trim() for all data properties.

: Implement the ability to interact with CBWIRE component from JavaScript using cbwire.find( '#args._id#' ).

: Add configuration setting 'enableTurbo' to automatically include everything needed to work with Turbo for single page applications.

: Add ability to call reset() without passing a key to reset all data properties to their original values.

: Implement onMount() method instead of mount().

: DocBox generated docs are failing because of file structure.

: Listeners are being fired immediately when calling emit() when the listener is defined on the same component, which they shouldn't.

: onHydrate() is firing after actions are performed.

: Computed properties are not being rendered before actions are called.

Are you tired of the challenges that come with building modern CFML apps? makes server-side app development a breeze, but sometimes the client-side is a whole different story. With powerful JavaScript frameworks like Vue and React, it can be a difficult and time-consuming process to create your web apps.

Install , then from our terminal, run:

Let's define our counter .

Finally, let's create a for our counter Wire. Notice that we've added a wire:click to our button.

We never left CFML.

CBWIRE is built on and wouldn't exist without ( creator of , ) and the PHP community.

The CBWIRE module for ColdBox is written and maintained by , , and .

Please consider becoming one of our lovingly esteemed .

CBWIRE Examples:

ForgeBox:

GitHub Repository:

API Docs:

Issue Tracker:

Task List Demo:

Form Validation Demo:

Up and Running Screencast:

Into The Box 2021 Presentation:

Ortus Webinar 2022:

Configuration
this guide
Data Properties
Wires
Actions
Wires
Template
Directives
Actions
Wires
Actions
Data Properties
Wire Template
Actions
Templates
Wires
TestBox
Wires
Data Property
Computed Property
Data Property
Action
Template
Template
Data Property
Data Property
Loading States API
Wires
WireBox
Template
Actions
Event
Wire
Listeners
<div wire:offline><!-- Oh no, you're offline --></div>
<div wire:offline.class="online" class="online"></div>
<div wire:offline.class.remove="im-online" class="im-online"></div>
<div wire:poll></div>
<div wire:poll.5s> <!-- poll every 5 seconds --> </div>
<div wire:poll="refreshTasks"></div>
component extends="cbwire.models.Component" {
    
    function refreshTasks() {}
}
<cfif args.shouldPoll>
    <div wire:poll="refreshTasks"></div>
<cfelse>
    <div><!-- No more polling --></div>
</cfif>
component extends="cbwire.models.Component"{

    // Action
    function clickButton() {
        // Relocate to Google
        relocate( url="https://www.google.com" );
        
        // Relocate using URI
        relocate( uri="/some/relative/path" );
        
        // Relocate using event and set variables in Flash Ram
        relocate( event="example.index", persistStruct={
            success: true
        } );
    }

}
/**
 * Relocate user browser requests to other events, URLs, or URIs.
 *
 * @event             The name of the event to relocate to, if not passed, then it will use the default event found in your configuration file.
 * @queryString       The query string or a struct to append, if needed. If in SES mode it will be translated to convention name value pairs
 * @addToken          Wether to add the tokens or not to the relocation. Default is false
 * @persist           What request collection keys to persist in flash RAM automatically for you
 * @persistStruct     A structure of key-value pairs to persist in flash RAM automatically for you
 * @ssl               Whether to relocate in SSL or not. You need to explicitly say TRUE or FALSE if going out from SSL. If none passed, we look at the even's SES base URL (if in SES mode)
 * @baseURL           Use this baseURL instead of the index.cfm that is used by default. You can use this for SSL or any full base url you would like to use. Ex: https://mysite.com/index.cfm
 * @postProcessExempt Do not fire the postProcess interceptors, by default it does
 * @URL               The full URL you would like to relocate to instead of an event: ex: URL='http://www.google.com'
 * @URI               The relative URI you would like to relocate to instead of an event: ex: URI='/mypath/awesome/here'
 *
 * @return void
 */
function relocate(
	event                = "",
	queryString          = "",
	boolean addToken     = false,
	persist              = "",
	struct persistStruct = structNew()
	boolean ssl,
	baseURL                   = "",
	boolean postProcessExempt = false,
	URL,
	URI
)
<div wire:key="foo"></div>
<button wire:click="foo">...</button>

<a href="" wire:click.prevent="foo">Click here</a>
<button wire:click.prefetch="foo">...</button>
<form wire:submit.prevent="someAction">
    <button type="submit">Submit Form</button>
</form>
<input wire:keydown="foo" type="text">
<input wire:keydown.enter="foo" type="text">
<select wire:foo="bar"></select>
<input wire:model="foo" type="text">
<input wire:model.debounce.1s="foo" type="text">
<input wire:model.debounce.500ms="foo" type="text">
<input wire:model.defer="foo" type="text">
<input wire:model.lazy="foo" type="text">
<div wire:poll></div>
<div wire:poll.5s></div>
<div wire:poll.5000ms></div>
<div wire:poll.5s="fooMethod"></div>
<div wire:init="foo"></div>
<span wire:loading><img src="spinner.gif"></span>
<div wire:loading.class="highlight"></div>
<div wire:loading.class.remove="highlight" class="highlight"></div>
<button wire:loading.attr="disabled">...</button>
<div wire:dirty="foo">...</div>
<div wire:dirty.class="highlight">...</div>
<div wire:dirty.class.remove="highlight" class="highlight">...</div>
<button wire:dirty.attr="disabled">...</div>
<button wire:dirty.attr="disabled" wire:target="foo">...</div>
<div wire:ignore></div>
<div wire:ignore.self></div>

stop

Equivalent of event.stopPropagation()

prevent

Equivalent of event.preventDefault()

self

Only triggers an action if the event was triggered on itself. This prevents outer elements from catching events that were triggered from a child element. (Like often in the case of registering a listener on a modal backdrop)

debounce.300ms

Adds an Xms debounce to the handling of the action.

<a href="" wire:click.prevent="someAction">Click here</a>

Backspace

backspace

Escape

escape

Shift

shift

Tab

tab

ArrowRight

arrow-right

<input wire:keydown.page-down="foo">
component extends="cbwire.models.Component"{

    function addTask(){
        sleep( 5000 ); // Optimize this code yo!
    }

}
<div>
    <button wire:click="addTask">Add Task</button>

    <div wire:loading>
        Adding Task...
    </div>
</div>
<div>
    <button wire:click="addTask" wire:loading.attr="disabled">
        Add Task
    </button>
</div>
<div>
    <button wire:click="addTask">Add Task</button>
 
    <div wire:loading wire:target="addTask">
       Adding task...
    </div>
</div>
<div wire:loading.delay>...</div>
<div wire:loading.delay.shortest>...</div> <!-- 50ms -->
<div wire:loading.delay.shorter>...</div>  <!-- 100ms -->
<div wire:loading.delay.short>...</div>    <!-- 150ms -->
<div wire:loading.delay>...</div>          <!-- 200ms -->
<div wire:loading.delay.long>...</div>     <!-- 300ms -->
<div wire:loading.delay.longer>...</div>   <!-- 500ms -->
<div wire:loading.delay.longest>...</div>  <!-- 1000ms -->
<div wire:loading.flex>...</div>
<div wire:loading.grid>...</div>
<div wire:loading.inline>...</div>
<div wire:loading.table>...</div>
<div>
    <button wire:click="addTask">Add Task</button>

    <div wire:loading.remove>
        Click 'Add Task'
    </div>
</div>
<div>
    <button wire:click.prefetch="togglePreview">Show Preview</button>

    <cfif args.showPreview>
        <!--- Preview goes here --->
    </cfif>
</div>
component extends="cbwire.models.Component"{

    data = {
        "showPreview": false
    };

    function togglePreview(){
        data.showPreview = true;
    }
}
mkdir cbwire-demo
cd cbwire-demo
box coldbox create app
box install cbwire@be
box server start
<!--- ./layouts/Main.cfm --->
<cfoutput>
<!doctype html>
<html>
<head>
    #wireStyles()#
</head>
<body>
    #wire( "Counter" )#
    #wireScripts()#
</body>
</html>
</cfoutput>
// ./wires/Counter.cfc
component extends="cbwire.models.Component" {

    // Data Properties
    data = {
        "counter": 0
    };

    // Actions
    function increment(){
        data.counter += 1;
    }
}
<!--- ./views/wires/counter.cfm --->
<cfoutput>
<div>
    <div>Count: #args.counter#</div>
    <button wire:click="increment">
        +
    </button>
</div>
</cfoutput>

Dirty State

Track changes to Data Properties and display changes in your UI instantly.

There are cases where it may be helpful to provide feedback that content has changed and is not yet in sync with the back-end. For input that uses wire:model, or wire:model.lazy, you can display that a field is 'dirty' until CBWIRE has fully updated.

Toggle Dirty Elements

Adding the .class modifier allows you to add a class to the element when dirty.

<div>
    <input wire:dirty.class="border-red-500" wire:model.lazy="foo">
</div>

You can perform the inverse and remove classes by adding the .remove modifier.

<div>
    <input wire:dirty.class.remove="bg-green-200" class="bg-green-200" wire:model.lazy="foo">
</div>

Toggle Elements

The default behavior of the wire:dirty directive without modifiers is that the element is hidden until dirty. This can create a paradox if used on the input itself, but like loading states, the dirty directive can be used to toggle the appearance of other elements using wire:target.

In this example, the span is hidden by default and only visible when the input element is dirty.

<div>
    <span wire:dirty wire:target="foo">Updating...</span>
    <input wire:model.lazy="foo">
</div>

Toggle Other Elements

Use the class and attribute modifiers in the same way for referenced elements.

<div>
    <label wire:dirty.class="text-red-500" wire:target="foo">Full Name</label>
    <input wire:model.lazy="foo">
</div>
👋
🤓
Wire
Directive
Actions
Templates
Wire
Action
Action
Action
Action
Action
Data Properties
Data Properties
Action
Data Properties
Action
See Polling
Action
Wire
Action
Offline State
KeyboardEvent.key
Action
Actions
Action's
CBWIRE-99
CBWIRE-100
CBWIRE-103
CBWIRE-124
CBWIRE-125
CBWIRE-130
CBWIRE-93
CBWIRE-121
CBWIRE-126
CBWIRE-127
CBWIRE-129
ColdBox
CommandBox
Wire
Template
directive
Livewire
Caleb Porzio
Livewire
Alpine.js
Grant Copley
Luis Majano
Ortus Solutions
Patreon supporters
https://github.com/grantcopley/cbwire-examples
https://forgebox.io/view/cbwire
https://github.com/coldbox-modules/cbwire
https://apidocs.ortussolutions.com/#/coldbox-modules/cbwire/
https://github.com/coldbox-modules/cbwire/issues
https://github.com/grantcopley/cbwire-task-list-demo
https://github.com/grantcopley/cbwire-signup-form-demo
https://cfcasts.com/series/ortus-single-video-series/videos/up-and-running-with-cbwire
https://cfcasts.com/series/into-the-box-2021/videos/cbwire-coldbox-+-livewire-grant-copley
https://cfcasts.com/series/ortus-webinars-2022/videos/grant-copley-on-cbwire-+-alpine_js
modifier
modifier

Defer Loading

Call an action immediately once your Wire is first rendered.

This can be helpful in cases where you don't want to hold up the entire page load, but want to load some data immediately after the page load.

<div wire:init="loadTasks">
    <ul>
        <cfloop array="#args.tasks#" index="task">
            <li>#task#</li>
        </cfloop>
    </ul>
</div>
component extends="cbwire.models.Component" {

    property name="taskService" inject="TaskService@myapp";
    
    data = {
        "tasks": []
    };
    
    function loadTasks(){
        data.tasks = taskService.asMemento().get();
    }
    
}

Inline Scripts

You can listen for livewire:load and place any JavaScript there.

<div>
    <!--- Your component's template --->
    <script>
        document.addEventListener('livewire:load', function () {
            // Your JS here.
        })
    </script>
</div>

AlpineJS

Beautifully integrate your client-side JavaScript and CBWIRE using AlpineJS.

AlpineJS allows you to add JavaScript behavior directly into your markup in a declarative way. If you are familiar with VueJS, Alpine should feel similar.

Installation

<head>
    <script src="//unpkg.com/alpinejs" defer></script>
    <!-- The "defer" attribute is important to ensure Alpine waits for CBWIRE to load first. -->
</head>

Templates

<div>
    <div x-data="{ open: false }">
        <button @click="open = true">Show More...</button>
 
        <ul x-show="open" @click.away="open = false">
            <li><button wire:click="archive">Archive</button></li>
            <li><button wire:click="delete">Delete</button></li>
        </ul>
    </div>
</div>

Entangle: Share State Between CBWIRE And AlpineJS

You need CBWIRE v2.3.6 or greater to use entangle.

CBWIRE has a powerful entangle() method that allows you to "entangle" a CBWIRE and AlpineJS data property. With entanglement, both client-side and server-side properties are instantly synchronized, regardless of whether the value was changed server-side in CFML or client-side using JavaScript.

This provides data model binding both client-side and server-side.

// wires/Counter.cfc
component extends="cbwire.models.Component" {

    data = {
        "counter": 0
    };

    function increment() {
        data.counter += 1;
    }
}

And now, our template:

<!--- views/wires/counter.cfm --->
<cfoutput>
    <div x-data="{ counter: #entangle( 'counter' )# }">
        <div>CBWIRE value: #args.counter#</div>
        <div>AlpineJS value: <span x-html="counter"></span></div>
        
        <button
            wire:click="increment"
            type="button">Increment with CBWIRE</button>
        
        <button
            @click="counter += 1"
            type="button">Increment with AlpineJS</button>
    </div>
</cfoutput>

We define an AlpineJS property named counter and then call the built-in CBWIRE method entangle(), passing it the name of the server-side data property we want to bind with.

<div x-data="{ counter: #entangle( 'counter' )# }">

Next, we are incrementing our Counter in two separate ways:

<button wire:click="increment" type="button">Increment with CBWIRE</button>
<button @click="counter += 1" type="button">Increment with AlpineJS</button>

Updating CBWIRE server-side on every AlpineJS property change is optional. You can also delay the server-side updates until the next CBWIRE request that goes out by chaining a .defer modifier like so.

<div x-data="{ counter: #entangle( 'counter' )#.defer }">

ContentBox CMS

You can use CBWIRE alongside ContentBox to build modernize, reactive applications with a powerful CMS included.

Installation

You need to install CBWIRE version 2.3.3 or later in order to be able to use CBWIRE and ContentBox.

box install cbwire@be

Once installed, you will need to restart your app using ?fwreinit=[your secret key].

Ensure that CBWIRE is installed under /modules/cbwire from the root of your project. It should be installed there by default. Installing CBWIRE elsewhere can cause errors within a ContentBox application.

Theme Integration

You will need to use a custom theme in order to integrate with CBWIRE.

If you are wanting to use a theme that is included with ContentBox or one that you've pulled down from ForgeBox, you can copy the theme code over to the folder modules_app/contentbox-custom/_themes.

Once CBWIRE is installed, you should have access to the three core included methods and should be able to reference these within your layout and theme templates.

  • wireStyles() - Pulls in required styling for CBWIRE

  • wireScripts() - Pulls in required JavaScript assets for CBWIRE/Livewire

In your theme layout, you need to place wireStyles() within your <head> tags, wireScripts() before the end of </body>, and you can call wire() to include your reactive UI Wires where needed.

<!--- modules_app/contentbox-custom/_themes/cbwireTheme/layouts/blog.cfm --->
<cfoutput>
<!--- Global Layout Arguments --->
<cfparam name="args.print" default="false">
<cfparam name="args.sidebar" default="true">

<!DOCTYPE html>
<html lang="en">
<head>
	<!--- Page Includes --->
	#cb.quickView( "_blogIncludes" )#

	<!--- ContentBoxEvent --->
	#cb.event( "cbui_beforeHeadEnd" )#

	<!--- PULL IN CBWIRE STYLING --->
	#wireStyles()#
</head>
<body>
	<!--- ContentBoxEvent --->
	#cb.event( "cbui_afterBodyStart" )#

	<!--- Header --->
	#cb.quickView( '_header' )#

	<!--- ContentBoxEvent --->
	#cb.event( "cbui_beforeContent" )#

	<!--- Main View --->
	#cb.mainView( args=args )#
	
	<!--- INCLUDE REACTIVE NEWSLETTER SIGNUP COMPONENT --->
	#wire( "NewsletterSignup", {
		validate: true
	} )#

	<!--- ContentBoxEvent --->
	#cb.event( "cbui_afterContent" )#

	#cb.quickView( view='_footer' )#

	<!--- ContentBoxEvent --->
	#cb.event( "cbui_beforeBodyEnd" )#

	<!--- PULL IN CBWIRE JAVASCRIPT ASSETS --->
	#wireScripts()#
</body>
</html>
</cfoutput>

You can also reference the wire() method within your views.

<!--- modules_app/contentbox-custom/_themes/cbwireTheme/views/index.cfm --->
<div class="row">
    <div class="col-12">
        #wire( "ContactForm" )#
    </div>
</div>

Turbo

Remove full-page reloads from your applications and create single-page applications with ease using Turbo.

Turbo is an open-source library that accelerates links and form submissions by negating the need for full page reloads. With Turbo, you get all the following included:

  • Links and form submissions are performed in the background using AJAX instead of performing full page reloads

  • Browse history management, so clicking the back and forward buttons in the browser work as expected

  • Internal caching that improves perceived performance by showing temporary previews during network requests and while the page is updated

  • And much more!

CBWIRE and Turbo together allow you to quickly build single-page applications in record time.

Enabling Turbo

// File: ./config/ColdBox.cfc
component{
    function configure() {
        moduleSettings = {
            "cbwire" = {
                "enableTurbo": true
            }
        };
     }
}

Manual Installation

Use the manual installation when you want more control over where Turbo is included within your HTML.

npm

You can install Turbo by running the following npm command in the root of your project.

npm i @hotwired/turbo

Then you can require or import Turbo.

import * as Turbo from "@hotwired/turbo"

Skypack

<script type="module">
    import hotwiredTurbo from 'https://cdn.skypack.dev/@hotwired/turbo';
</script>

Livewire/Turbo Plugin

For Turbo to work properly with Livewire (and therefore CBWIRE), you will also need to include the Turbo plugin below your wireScripts() call in your layout.

    #wireScripts()#
    <script src="https://cdn.jsdelivr.net/gh/livewire/turbolinks@v0.1.x/dist/livewire-turbolinks.js" data-turbolinks-eval="false" data-turbo-eval="false"></script>
</body>

Note: You MUST have either the data-turbolinks-eval="false" or data-turbo-eval="false" attributes added to the script tag (having both won't hurt).

Displaying Progress

During Turbo navigation, the browser will not display its native progress indicator. Turbo installs a CSS-based progress bar to provide feedback while issuing a request.

The progress bar is enabled by default. It appears automatically for any page that takes longer than 500ms to load.

The progress bar is a <div> element with the class name turbo-progress-bar. Its default styles appear first in the document and can be overridden by rules that come later.

For example, the following CSS will result in a thick green progress bar:

.turbo-progress-bar {
    height: 5px;
    background-color: green;
}

In your layout file, you can include the progress bar like so:

<!-- ./layouts/Main.cfm -->
<cfoutput>
<html>
    <head>
        <title>Turbo Page</title>
        #wireStyles()#
    </head>
    <body>
        <div class="turbo-progress-bar"></div>
        <div class="mainContent">
            Some content here
        </div>
        #wireScripts()#
    </body>
</html>
</cfoutput>

Preload Links into Turbo's Cache

Preload links into Turbo Drive’s cache using data-turbo-preload.

<a href="/" data-turbo-preload>Home</a>

This will make page transitions feel lightning fast by providing a preview of a page even before the first visit. Use it to preload the most important pages in your application.

Avoid over usage, as it will lead to loading content that is not needed.

Query String

Example

https://yourapp.com/search-articles?search=some+string
component extends="cbwire.models.Component" {

    data = {
        "search": ""
    };
    
    queryString = [ "search" ];
}
<div>
    <input wire:model="search" type="search" placeholder="Search articles...">
</div>

You can use the wire:init to execute an action as soon as your component is initially rendered.

We recommend you use for most of your JavaScript needs, but you can use <script> tags directly inside your .

Your scripts will be run only once upon the first render of the component. If you need to run a JavaScript function later, you can emit the from the component and listen to it in JavaScript.

Many page interactions don't warrant a full server roundtrip, such as toggling a modal or a hidden element. In these instances, we recommend using .

For more installation information, visit the .

Below is an example of using to toggle a list on the page.

Consider this simple Counter :

Incrementing the counter by calling the increment using CBWIRE

Incrementing the AlpineJS property counter value in JavaScript, triggering an immediate update to the server and re-rendering of the Counter .

is a professional open source hybrid modular CMS (Content Management System) that allows you to easily build websites, blogs, wikis, complex web applications and even power mobile or cloud applications. Built with a secure and flexible modular core, designed to scale, and combined with world-class support, ContentBox will get your projects out the door in no time.

In the root of your ContentBox application, install CBWIRE using . The example below installs the latest bleeding-edge version.

wires() - Includes a (reactive UI element) you create

For additional information on how Turbo can be used and configured, please see

You can enable Turbo with the enableTurbo , which automatically wires up everything needed to start using Turbo. Once enabled, links and form submissions will automatically be performed in the background via AJAX instead of performing full page reloads.

You can alternatively perform a of Turbo instead.

To avoid manual installation, there is also a available which you can add to the <head></head> of your layout.

It can be useful at times to update the browser's query string when your state changes.

Let's say you are building a to search articles, and want the query string to reflect the current search value like so:

This way, when a user hits the back button or bookmarks the page, you can get the initial state out of the query string rather than resetting the every time.

You can add a queryString variable to your and CBWIRE will update the query string every time the property value changes and also update the property when the query string changes.

Directive
AlpineJS
Templates
Event
AlpineJS
Alpine Docs
AlpineJS
Wire
Action
Wire
ContentBox
CommandBox
Wire
https://turbo.hotwired.dev/
configuration setting
Skypack
Wire's
Wire
Wire
Wires
Built-in progress bar
Pre-load Links
manual installation
Homepage listing various CBWIRE examples
Individual page showing form validation in CBWIRE