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.

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

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.

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

  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.

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

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:

The increment method updates the counter state by 1.

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

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

  • We never left CFML.🤓
    API Docs:
  • Issue Tracker:

  • Task List Demo:

  • Form Validation Demo:

  • Up and Running Screencast:

  • Into The Box 2021 Presentation:

  • Ortus Webinar 2022:

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

    What!? How?

    Awesome, right?

    Credits

    Project Support

    Resources

    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

    Inline Scripts

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

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

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

    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.

    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:

    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.

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

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

    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.

    What's New With 2.1

    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

    Event
    Action
    Wire
    <!--- 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>
    <div x-data="{ counter: #entangle( 'counter' )# }">
    <button wire:click="increment" type="button">Increment with CBWIRE</button>
    <button @click="counter += 1" type="button">Increment with AlpineJS</button>
    <div x-data="{ counter: #entangle( 'counter' )#.defer }">

    Nested components are not rendering

    11/26/2022

    Added

    Fixed

    CBWIRE-111
    CBWIRE-119
    CBWIRE-118
    CBWIRE-117
    CBWIRE-116
    CBWIRE-115
    CBWIRE-96

    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>

    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 )

    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

    What's New With 2.2

    01/09/2022

    Added

    CBWIRE-99: Implement lifecycle hook onHydrate().

    CBWIRE-100: Implement lifecycle hook onHydrate[Property]().

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

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

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

    CBWIRE-130: 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().

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

    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

    Configuration

    Alter CBWIRE's behavior with these nifty configuration options.

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

    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.

    The maximum amount of time allowed for uploads to complete.

    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.

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

    Redirecting

    Similar to ColdBox, you can relocate users using relocate.

    You can redirect users in your using 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.

    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.

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

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

    Prefetching

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

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

    Query String

    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.

    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.

    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.

    The default behavior of the wire:dirty

    Dependency Injection

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

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

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

    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.

    Fixed

    Fixed

    CBWIRE-93
    CBWIRE-121
    CBWIRE-126
    CBWIRE-127
    CBWIRE-129
    Defaults to false
    .

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

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

    ColdBox.cfc

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

    enableTurbo

    maxUploadSeconds

    throwOnMissingSetter

    trimStringValues

    Turbo
    Wire

    wiresLocation

    useComputedPropertiesProxy

    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

    Actions
    <div wire:poll></div>

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

    Method Invocation

    Cancel Polling

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

    Action's
    https://yourapp.com/search-articles?search=some+string
    component extends="cbwire.models.Component" {
    
        data = {
            "search": ""
        };
        
        queryString = [ "search" ];
    }

    Example

    Wire's
    Wire
    Wire
    Wires
    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" ); 
        }
    
    }

    WireBox

    onDIComplete

    Wires
    WireBox
    <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();
        }
        
    }

    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.

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

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

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

    Toggle Dirty Elements

    Toggle Elements

    Toggle Other Elements

    // File: ./config/ColdBox.cfc
    component{
        function configure() {
            moduleSettings = {
                cbwire = {
                    "enableTurbo": false,
                    "maxUploadSeconds": 5 * 60, // 5 minutes
                    "throwOnMissingSetterMethod" : false,
                    "trimStringValues": false,
                    "wiresLocation": "myWires",
                    "useComputedPropertiesProxy": false
                }
            };
         }
    }
    
    /**
     * 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: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>
    <div>
        <input wire:model="search" type="search" placeholder="Search articles...">
    </div>
    component extends="cbwire.models.Component" {
    
        property name="storage" inject="cbfs:disks:temp";
    
        function onDIComplete(){
            storage.create( "log.txt", "Dependency Injection Complete!" );
        }
    
    }
    <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>

    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.

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

    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:

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

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

    Templates

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

    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.

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

    You can specify the path to your Template using template.

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

    See the page for additional details.

    Testing

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

    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.

    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.

    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

    Actions

    Layout Setup

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

    Inserting Wires

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

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

    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

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

    Explicit Path

    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

    Wire
    Wire Lifecycle

    Outer Element

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

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

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

    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.

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

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

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

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

    Test Example

    Test Methods

    data

    computed

    toggle

    call

    Wires
    TestBox
    Wires
    Data Property
    Computed Property
    Data Property

    emit

    see

    dontSee

    seeData

    dontSeeData

    Actions Methods that make state changes to our data properties.
  • Events and Listeners Listen for emitted events within Wires or client-side JavaScript.

  • Templates HTML to render our Wire.

  • You can scaffold Wires quickly using the commandbox-cbwire 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.

    Data Properties
    Computed Properties

    Basic Example

    Scaffolding

    component extends="cbwire.models.Component"{
        
        listeners = {
            "taskAdded": "clearCache"
        };
    
        function clearCache(){}
    }
    <script>
        cbwire.on( 'taskAdded', task => {
            console.log( 'A task was added. ');
        })
    </script>
    box install cbwire@be
    <!--- ./layouts/Main.cfm --->
    <cfoutput>
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <title>CBWIRE Example</title>
            #wireStyles()#
        </head>
        <body>
            
            <!--- JavasScript references below --->
            #wireScripts()#
        </body>
    </html>
    </cfoutput>
    <!--- ./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>
    component extends="cbwire.models.Component" {
        // No template defined.
    }
    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
    }
    component extends="cbwire.model.Component"{
        // Action
        function updateSession(){
            // prevent UI updating
            noRender();
        }
    }
    <!--- 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>
    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>" );
    		} );
    	
    	} );
        }
    }
    wire( "TaskList" )
        .data( "tasks", [ "task1", "task2" ] ) // one property at a time
        .data( { "tasks" : [ "task1", "task2" ] } ); // struct of properties
    wire( "TaskList" )
        .computed( "count", function() { return 3; } ) // one property at a time
        .computed( { "count" : function() { return 3; } } ); // struct of properties
    wire( "TaskList" ).toggle( "showModal" );
    wire( "TaskList" ).call( "clearTasks" );
    wire( "TaskList" ).call( "clearTasks", [ param1, param2 ] );
    wire( "TaskList" ).emit( "addedTask" );
    wire( "TaskList" ).emit( "addedTask", [ param1, param2 ] );
    wire( "TaskList" ).see( "<h1>My Tasks</h1>" );
    wire( "TaskList" ).dontSee( "<h1>Someone Else's Tasks</h1> ");
    wire( "TaskList" ).seeData( "tasksRemoved", true );
    wire( "TaskList" ).dontSeeData( "tasksRemoved", true );
    // 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

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

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

    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.

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

    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 rendering

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

    1. onHydrate[DataProperty]()

    2. onHydrate()

    3. Render

    Runs only once when the Wire is initially wired.

    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 .

    Computed Properties ( Proxied )

    Create dynamic properties for your UI Wires.

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

    Otherwise, please follow 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.

    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.

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

    ContentBox CMS

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

    ContentBox 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 CommandBox. The example below installs the latest bleeding-edge version.

    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

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

    Validation

    Wires include powerful validations that you can use to validate your Data Properties.

    Validations are provided by .

    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

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

    validate

    Returns a ValidateResult object.

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

    validateOrFail

    Silently fails and prevents further processing of the current .

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

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

    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.

    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:

    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.

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

    JavaScript

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

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

    Hook
    Description

    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 .

    You can define and initialize properties on your using data.

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

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

    See the Wire Lifecycle 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 Template or from within a wire Action, it will only execute once.

    Defining Properties

    Accessing Properties

    Getters

    Templates

    Wires
    Actions
    Template
    this guide here
    Execute Actions
  • onRender() or implicit Template rendering

  • onMount

    This does not fire during subsequent renderings for the Wire.

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

    onHydrate

    onHydrate[ DataProperty ]

    onRender

    Template
    Computed Properties
    Computed Properties
    Action
    Data Property
    Computed Properties
    Action
    Wire
    Data Properties
    Computed Properties
    The ability to invoke Computed Properties anywhere they are needed (both Actions and Templates).
  • The ability to cache Computed Property results to enhance performance.

  • Defining Properties

    Accessing From Actions

    Accessing From Templates

    Wires
    Actions
    Template
    Configuration
    this guide
    Wire

    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

    Displaying Errors

    Action
    Templates
    cbValidation

    Delaying

    Display Property

    Hiding

    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

    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.

    component.initialized

    Called when a Wire has been initialized on the page by Livewire

    element.initialized

    Lifecycle Hooks

    Called when Livewire initializes an individual element

    Interacting With Wires

    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>
    component extends="cbwire.models.Component" {
    
        data = {
            "taskId": 0
        };
    
        function onMount( event, rc, prc, parameters ){
            data.taskId = event.getValue( "taskId" );
        }
    
    }
    
    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>
            ";
        }
    }
    component extends="cbwire.models.Component" {
    
        data = {
            "count": 1
        };
    
        function onHydrateCount( data ) {
            // Note that computed properties are not rendered yet
            data.count += 1;
        }  
    }
    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>
            ";
        }
    }
    component extends="cbwire.models.Component" {
    
        property name="taskService" inject="taskService@myapp";
    
        // Computed Properties
        computed = {
            "allTasks": function() {
                return taskService.getAll();
            }
        };
    }
    
    component extends="cbwire.models.Component" {
    
        // Computed Properties
        computed = {
            "allTasks": function() {
                // return something here
            }
        };
    
        // Action
        function deleteTasks() {      
    
            if ( arrayLen( computed.allTasks() ) {
                taskService.deleteAll();
            }
    
        }
    }
    <cfoutput>
    <div>
        <ul>
            <cfloop array="#args.computed.allTasks()#" index="task">
                <li>#task#</li>    
            </cfloop>
        </ul>
    </div>
    </cfoutput>
    <!--- 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>
    component extends="cbwire.models.Component"{
        // Action
        function addTask() {
            validateOrFail();
    
            queryExecute( ... );        
        }
    }
    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" }
                }
            );
        }
    }
    <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>
    <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>
    <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>
    <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>
    You can reference data properties within your Wire's template using args.[propertyName].

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

    There are several ways to use reset.

    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.

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

    Defining Properties

    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 Computed Properties instead.

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

    Accessing Properties

    Data Variable

    Getters

    You can use this as an alternative to using data.

    Templates

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

    Resetting Properties

    Security

    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.

    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

    Here are a few examples of each in HTML:

    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:

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

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

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

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

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

    Function
    Description

    Consider the example below.

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

    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:

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

    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.

    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

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

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

    This works with the entire .

    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.

    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.

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

    Then you can require or import Turbo.

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

    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.

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

    DOM

    click

    wire:click

    keydown

    wire:keydown

    submit

    wire:submit

    $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

    Passing Parameters

    Return Values

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

    Accessing Data Properties

    Accessing Computed Properties

    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

    Actions

    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.

    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.

    FileUpload Object

    Loading Indicators

    Loading States API

    For additional information on how Turbo can be used and configured, please see https://turbo.hotwired.dev/

    Enabling Turbo

    Manual Installation

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

    npm

    Skypack

    Livewire/Turbo Plugin

    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

    Preload Links into Turbo's Cache

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

    Built-in progress bar
    Pre-load Links
    configuration setting
    manual installation
    Skypack
    <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>
    <button wire:foo="someAction">
    <button wire:click="addTask('Some Task')">Add Task</button>
    // Action
    function addTask( taskName ){
    
    }
        data = {
            "task": ""
        };
    
        // Actions
        function clearTasks(){
            data.task = "";
        }
        // Computed Properties
        computed = {
            "tasks": function() {
                return queryExecute( "
                    select *
                    from tasks
                " );
    
            }
        };
    
        // Actions
        function deleteTasks(){
            if ( computed.tasks.recordCount ) {
                // query to delete the tasks...    
            } 
        }
    <div>
        #args.message#
        <button wire:click="setMessageToHello">Say Hi</button>
    </div>
    <div>
        #args.message#
        <button wire:click="$set( 'message', 'Hello' )">Say Hi</button>
    </div>
    function someAction() {
        emit( "some-event" );
    }
    component {
        listeners = {
            "some-event": "$refresh"
        };
    }
    <input type="file" wire:model="photo">
     
    <div wire:loading wire:target="photo">Uploading...</div>
    // 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/turbolinks@v0.1.x/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>

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

    Listeners for a submit event on a 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.

    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.

    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.

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

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

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

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

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

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

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

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

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

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

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

    See

    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

    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.

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

    Native Browser Event
    Livewire Modifier

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

    debounce.300ms

    Adds an Xms debounce to the handling of the action.

    Tab

    tab

    ArrowRight

    arrow-right

    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)

    Backspace

    backspace

    Escape

    escape

    Shift

    shift

    wire:submit

    You will want to add the .prevent modifier to ensure that the browser doesn't submit the 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

    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

    wire:loading.class

    wire:loading.class.remove

    wire:loading.attr

    wire:dirty

    wire:dirty.class

    wire:dirty.class.remove

    wire:dirty.attr

    wire:target

    wire:ignore

    wire:ignore.self

    wire:offline

    Event Modifiers

    Keydown Modifiers

    Action
    Action
    Action
    Action
    Data Properties
    Data Properties
    Action
    Data Properties
    Action
    See Polling
    Action
    Wire
    Action
    Offline State
    KeyboardEvent.key
    Action
    modifier
    <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>
    <a href="" wire:click.prevent="someAction">Click here</a>
    <input wire:keydown.page-down="foo">