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.

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

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.

https://github.com/grantcopley/cbwire-examples

Homepage listing various CBWIRE examples
Individual page showing form validation in CBWIRE

What's New With 2.2

01/09/2022

Added

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

Changed

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

Fixed

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

What's New With 2.1

11/26/2022

Added

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

Fixed

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

CBWIRE-99
CBWIRE-100
CBWIRE-103
CBWIRE-124
CBWIRE-125
CBWIRE-130
CBWIRE-93
CBWIRE-121
CBWIRE-126
CBWIRE-127
CBWIRE-129
CBWIRE-111
CBWIRE-119
CBWIRE-118
CBWIRE-117
CBWIRE-116
CBWIRE-115
CBWIRE-96

Query String

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

Example

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

https://yourapp.com/search-articles?search=some+string

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 Wire every time.

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

component extends="cbwire.models.Component" {

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

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.

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.

enableTurbo

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.

maxUploadSeconds

The maximum amount of time allowed for uploads to complete.

throwOnMissingSetter

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.

trimStringValues

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

wiresLocation

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

useComputedPropertiesProxy

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

Defer Loading

Call an action immediately once your Wire is first rendered.

You can use the wire:init to execute an action as soon as your component is initially 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.

// File: ./config/ColdBox.cfc
component{
    function configure() {
        moduleSettings = {
            cbwire = {
                "enableTurbo": false,
                "maxUploadSeconds": 5 * 60, // 5 minutes
                "throwOnMissingSetterMethod" : false,
                "trimStringValues": false,
                "wiresLocation": "myWires",
                "useComputedPropertiesProxy": false
            }
        };
     }
}
Overriding Module Settings
Turbo
Wire
Wires
Computed Properties (Proxied)
<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();
    }
    
}
Directive

Events & Listeners

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

Emitting Events

You can emit events from Actions, Templates, and JavaScript.

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

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

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

Dependency Injection

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

WireBox

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

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!" );
    }

}

Prefetching

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

You can prefetch an Action's results on mouseOver using the .prefetch modifier.

<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;
    }
}

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.

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.

<div wire:offline><!-- Oh no, you're offline --></div>

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

<div wire:offline.class="online" class="online"></div>

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

<div wire:offline.class.remove="im-online" class="im-online"></div>

Wires

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

Wires primarily consist of the following:

  • 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.

Basic Example

Scaffolding

You can scaffold Wires quickly using the module.

From our CommandBox shell, type:

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

You can provide named arguments as well.

Computed Properties

Create dynamic properties for your UI Wires.

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 dynamic properties and are helpful for deriving values from a database or another persistent store like a cache.

Computed Properties are similar to with some key differences:

  • 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.

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.

Defining Properties

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

Accessing Properties

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

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.

Templates

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

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.

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

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.

Toggle Other Elements

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

Inline Scripts

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

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.

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.

<div>
    <input wire:dirty.class="border-red-500" wire:model.lazy="foo">
</div>
<div>
    <input wire:dirty.class.remove="bg-green-200" class="bg-green-200" wire:model.lazy="foo">
</div>
<div>
    <span wire:dirty wire:target="foo">Updating...</span>
    <input wire:model.lazy="foo">
</div>
<div>
    <label wire:dirty.class="text-red-500" wire:target="foo">Full Name</label>
    <input wire:model.lazy="foo">
</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>
// 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>
install commandbox-cbwire
cbwire create Counter
// Created /wires/Counter.cfc
// Created /views/wires/counter.cfm
// Created /tests/specs/integration/wires/CounterTest.cfc
cbwire create name=Counter views=false
// Created /wires/Counter.cfc
// Created /tests/specs/integration/wires/CounterTest.cfc
Data Properties
Computed Properties
Actions
Events and Listeners
Templates
commandbox-cbwire
component extends="cbwire.models.Component" {

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

    // Computed Properties
    computed = {
        "allTasks": function() {
            return taskService.getAll();
        }
    };
}
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();
        }

    }
}
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();
        }
    }
}
<cfoutput>
<div>
    <ul>
        <cfloop array="#args.allTasks#" index="task">
            <li>#task#</li>    
        </cfloop>
    </ul>
</div>
</cfoutput>
this guide here
Data Properties
Wire Lifecycle
Template
Action
Wires
Actions
Template
<div>
    <!--- Your component's template --->
    <script>
        document.addEventListener('livewire:load', function () {
            // Your JS here.
        })
    </script>
</div>
AlpineJS
Templates
Event

Testing

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

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

Test Example

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

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

Set a Data Property to the specified value.

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

computed

Set a Computed Property to the specified closure.

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

toggle

Toggles a Data Property between true and false.

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

call

Calls an Action. Optional array of parameters can be provided.

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

emit

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

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

see

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

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

dontSee

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

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

seeData

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

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

dontSeeData

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

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

Templates

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

Wires 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 Configuration

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

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

See the Wire Lifecycle page for additional details.

Prevent Rendering

Within your Actions, 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.

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

Loading States

Help website users during wait times using Loading States.

Actions 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.

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.

component extends="cbwire.models.Component"{

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

}

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

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

    <div wire:loading>
        Adding Task...
    </div>
</div>

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

Toggling Attributes

<div>
    <button wire:click="addTask" wire:loading.attr="disabled">
        Add Task
    </button>
</div>

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.

<div>
    <button wire:click="addTask">Add Task</button>
 
    <div wire:loading wire:target="addTask">
       Adding task...
    </div>
</div>

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.

<div wire:loading.delay>...</div>

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

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

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.

<div wire:loading.flex>...</div>
<div wire:loading.grid>...</div>
<div wire:loading.inline>...</div>
<div wire:loading.table>...</div>

Hiding

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

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

    <div wire:loading.remove>
        Click 'Add Task'
    </div>
</div>

Redirecting

Similar to ColdBox, you can relocate users using relocate.

You can redirect users in your Actions using relocate().

relocate

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

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
        } );
    }

}

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.

/**
 * 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
)

Getting Started

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

Requirements

  • Adobe ColdFusion 2018+ or Lucee 5+

  • ColdBox 6+

Installation

Install CommandBox.

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

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

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

Data Properties

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

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

Defining Properties

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.

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

Accessing Properties

Data Variable

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

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]().

You can use this as an alternative to using data.

Templates

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

Resetting Properties

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

There are several ways to use reset.

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.

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 .

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

Validation

include powerful validations that you can use to validate your .

Validations are provided by .

Defining Constraints

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

Validating

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

validate

Returns a ValidateResult object.

validateOrFail

Silently fails and prevents further processing of the current .

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();

Displaying Errors

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

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

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

    // Data properties
    data = {
        "task": ""
    };
    
    // Action
    function addTask(){
        taskService.create( this.getTask() );
    }
}
<cfoutput>
<div>
    <h1>Task</h1>
    <div>#args.task#</div>
</div>
</cfoutput>
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'
    }

}
function someAction() {
    reset( "task" ); // resets 'task' data property
    reset( [ "task", "anotherprop" ] ); // reset multiple properties at once
    reset(); // resets all properties
}
Wires
Wires
Computed Properties
DOM
component extends="cbwire.models.Component"{

    this.constraints = {
        "task": { required: true }
    };
    
    // Data Properties
    data = {
        "task": ""
    };
}
component extends="cbwire.models.Component"{
    // Action
    function addTask() {
        var result = validate(); // ValidateResult object
        
        if ( !result.hasErrors() ) {
            queryExecute( ... );
        }
    }
}
component extends="cbwire.models.Component"{
    // Action
    function addTask() {
        validateOrFail();

        queryExecute( ... );        
    }
}
component extends="cbwire.models.Component"{
    // Action
    function addTask() {
        var data = { "email": "[email protected]" };

        var validationManager = getValidationManager();
        
        var result = validationManager.validate(
            target=data,
            constraints={
                "email": { required: true, type: "email" }
            }
        );
    }
}
<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>
Wires
Data Properties
cbValidation
Actions
Action
Templates

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.

This works with the entire Loading States API.

AlpineJS

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

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

For more installation information, visit the Alpine Docs.

Templates

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

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

Consider this simple Counter Wire:

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

  • Incrementing the counter by calling the increment Action using CBWIRE

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

<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 }">

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.

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

Enabling Turbo

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.

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.

Then you can require or import Turbo.

Skypack

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

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.

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:

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

Preload Links into Turbo's Cache

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

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.

ContentBox CMS

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

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.

Installation

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

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

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

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

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.

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

// File: ./config/ColdBox.cfc
component{
    function configure() {
        moduleSettings = {
            "cbwire" = {
                "enableTurbo": true
            }
        };
     }
}
npm i @hotwired/turbo
import * as Turbo from "@hotwired/turbo"
<script type="module">
    import hotwiredTurbo from 'https://cdn.skypack.dev/@hotwired/turbo';
</script>
    #wireScripts()#
    <script src="https://cdn.jsdelivr.net/gh/livewire/[email protected]/dist/livewire-turbolinks.js" data-turbolinks-eval="false" data-turbo-eval="false"></script>
</body>
.turbo-progress-bar {
    height: 5px;
    background-color: green;
}
<!-- ./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>
<a href="/" data-turbo-preload>Home</a>
Built-in progress bar
Pre-load Links
https://turbo.hotwired.dev/
configuration setting
manual installation
Skypack
box install cbwire@be
<!--- 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>
<!--- modules_app/contentbox-custom/_themes/cbwireTheme/views/index.cfm --->
<div class="row">
    <div class="col-12">
        #wire( "ContactForm" )#
    </div>
</div>
ContentBox
CommandBox
Wire

Actions

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

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

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

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

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>

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

// 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"
    };
}

Directives

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

Core Directives

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

wire:key

<div wire:key="foo"></div>

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

<button wire:click="foo">...</button>

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

Listens for a click event and invokes the foo Action.

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

wire:click.prefetch

<button wire:click.prefetch="foo">...</button>

Listens for a mouseEnter event and then prefetches the result of the foo Action. 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.

wire:submit

<form wire:submit.prevent="someAction">
    <button type="submit">Submit Form</button>
</form>

Listeners for a submit event on a form.

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

wire:keydown

<input wire:keydown="foo" type="text">

Listens for a keyDown event and invokes the foo Action.

wire:keydown.enter

<input wire:keydown.enter="foo" type="text">

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

wire:foo

<select wire:foo="bar"></select>

Listens for a foo event and invokes the bar Action.

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

wire:model

<input wire:model="foo" type="text">

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

wire:model.debounce

<input wire:model.debounce.1s="foo" type="text">
<input wire:model.debounce.500ms="foo" type="text">

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

<input wire:model.defer="foo" type="text">

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

wire:model.lazy

<input wire:model.lazy="foo" type="text">

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

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

<div wire:poll></div>
<div wire:poll.5s></div>
<div wire:poll.5000ms></div>
<div wire:poll.5s="fooMethod"></div>

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 Action that you want to invoke. See Polling

wire:init

<div wire:init="foo"></div>

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

wire:loading

<span wire:loading><img src="spinner.gif"></span>

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

wire:loading.class

<div wire:loading.class="highlight"></div>

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

wire:loading.class.remove

<div wire:loading.class.remove="highlight" class="highlight"></div>

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

wire:loading.attr

<button wire:loading.attr="disabled">...</button>

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.

<div wire:dirty="foo">...</div>

wire:dirty.class

<div wire:dirty.class="highlight">...</div>

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

wire:dirty.class.remove

<div wire:dirty.class.remove="highlight" class="highlight">...</div>

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

wire:dirty.attr

<button wire:dirty.attr="disabled">...</div>

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

<button wire:dirty.attr="disabled" wire:target="foo">...</div>

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

wire:ignore

<div wire:ignore></div>

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

<div wire:ignore.self></div>

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

wire:offline

See Offline State

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.

Modifier

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>

Keydown Modifiers

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 KeyboardEvent.key as modifiers by converting them to kebab-case.

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

Native Browser Event
Livewire Modifier

Backspace

backspace

Escape

escape

Shift

shift

Tab

tab

ArrowRight

arrow-right

<input wire:keydown.page-down="foo">

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

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.

Are you tired of the challenges that come with building modern CFML apps? ColdBox 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.

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

Install CommandBox, then from our terminal, run:

mkdir cbwire-demo
cd cbwire-demo
box coldbox create app
box install cbwire@be
box server start

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" ).

<!--- ./layouts/Main.cfm --->
<cfoutput>
<!doctype html>
<html>
<head>
    #wireStyles()#
</head>
<body>
    #wire( "Counter" )#
    #wireScripts()#
</body>
</html>
</cfoutput>

Let's define our counter Wire.

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

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

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

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

<!--- ./views/wires/counter.cfm --->
<cfoutput>
<div>
    <div>Count: #args.counter#</div>
    <button wire:click="increment">
        +
    </button>
</div>
</cfoutput>

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.

  • We never left CFML.🤓

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

Credits

CBWIRE is built on Livewire and wouldn't exist without Caleb Porzio ( creator of Livewire, Alpine.js ) and the PHP community.

The CBWIRE module for ColdBox is written and maintained by Grant Copley, Luis Majano, and Ortus Solutions.

Project Support

Please consider becoming one of our lovingly esteemed Patreon supporters.

Resources

  • CBWIRE Examples: https://github.com/grantcopley/cbwire-examples

  • ForgeBox: https://forgebox.io/view/cbwire

  • GitHub Repository: https://github.com/coldbox-modules/cbwire

  • API Docs: https://apidocs.ortussolutions.com/#/coldbox-modules/cbwire/

  • Issue Tracker: https://github.com/coldbox-modules/cbwire/issues

  • Task List Demo: https://github.com/grantcopley/cbwire-task-list-demo

  • Form Validation Demo: https://github.com/grantcopley/cbwire-signup-form-demo

  • Up and Running Screencast: https://cfcasts.com/series/ortus-single-video-series/videos/up-and-running-with-cbwire

  • Into The Box 2021 Presentation: https://cfcasts.com/series/into-the-box-2021/videos/cbwire-coldbox-+-livewire-grant-copley

  • Ortus Webinar 2022: https://cfcasts.com/series/ortus-webinars-2022/videos/grant-copley-on-cbwire-+-alpine_js

Computed Properties ( Proxied )

Create dynamic properties for your UI Wires.

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

Otherwise, please follow this guide instead.

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.

Computed Properties are similar to Data Properties with some key differences:

  • They are declared as inline functions using computed.

  • They can return any CFML value or object.

Defining Properties

Define Computed Properties in your Wires using computed.

component extends="cbwire.models.Component" {

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

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

Accessing From Actions

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

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

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

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

Wire Lifecycle

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

Order of Operations

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

  1. onMount()

  2. Render Computed Properties

  3. onRender() or implicit Template rendering

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

  1. onHydrate[DataProperty]()

  2. onHydrate()

  3. Render Computed Properties

  4. Execute Actions

  5. onRender() or implicit Template rendering

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

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

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 ]

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

component extends="cbwire.models.Component" {

    data = {
        "count": 1
    };

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

onRender

Runs during the rendering of a Wire. 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 Data Properties and any rendered Computed Properties.

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>
        ";
    }
}

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

Called when a has been initialized on the page by Livewire

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

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

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