Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
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.
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
See CBWIRE in action!
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
: Implement lifecycle hook onHydrate().
: Implement lifecycle hook onHydrate[Property]().
: Implement an automatic trim() for all data properties.
: Implement the ability to interact with CBWIRE component from JavaScript using cbwire.find( '#args._id#' ).
: Add configuration setting 'enableTurbo' to automatically include everything needed to work with Turbo for single page applications.
: Add ability to call reset() without passing a key to reset all data properties to their original values.
: Implement onMount() method instead of mount().
: DocBox generated docs are failing because of file structure.
: Listeners are being fired immediately when calling emit() when the listener is defined on the same component, which they shouldn't.
: onHydrate() is firing after actions are performed.
: Computed properties are not being rendered before actions are called.
Ability to output component template direct from onRender() method instead of defining a .cfm template in wire/views.
Computed properties that do not return a value result in the error 'variable [VALUE] doesn't exist
Nested components are causing the template rendering only to render the last nested template
Struct values are not being passed to the template and are instead being replaced with an empty string
CBWIRE doesn't work when the ColdBox app is in a subdirectory
Getting errors when rendering component templates in the latest version of ColdBox
Nested components are not rendering
It can be useful at times to update the browser's query string when your Wire's state changes.
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>
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. Defaults to false.
The relative folder path where are stored. Defaults to 'wires'.
When set to true, Computed Properties will be proxied. See .
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
}
};
}
}
<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();
}
}
You can emit events from both your Wires and JavaScript. Superb!
You can emit events from Actions, Templates, and JavaScript.
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()
] );
}
Using the $emit()
method:
<button wire:click="$emit( 'taskAdded' )">
Using the global cbwire.emit()
:
<script>
cbwire.emit( 'taskAdded' );
</script>
You can register event listeners on a Wire by defining listeners
.
component extends="cbwire.models.Component"{
listeners = {
"taskAdded": "clearCache"
};
function clearCache(){}
}
JavaScript keys are case-sensitive. You can preserve the key casing in CFML by surrounding your listener names in quotations.
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>
Just as with ColdBox, you can inject your dependencies using 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" );
}
}
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!" );
}
}
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.
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 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.
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.
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.
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.
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
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.
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.
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.
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.
<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
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>
<div>
<!--- Your component's template --->
<script>
document.addEventListener('livewire:load', function () {
// Your JS here.
})
</script>
</div>
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.
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>" );
} );
} );
}
}
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
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
Toggles a Data Property between true and false.
wire( "TaskList" ).toggle( "showModal" );
Calls an Action. Optional array of parameters can be provided.
wire( "TaskList" ).call( "clearTasks" );
wire( "TaskList" ).call( "clearTasks", [ param1, param2 ] );
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 ] );
Verifies a value can be found in the current Template rendering. Otherwise, test fails.
wire( "TaskList" ).see( "<h1>My Tasks</h1>" );
Verifies a value is not found in the current Template rendering. Otherwise, test fails.
wire( "TaskList" ).dontSee( "<h1>Someone Else's Tasks</h1> ");
Verifies a Data Property matches a specified value. Otherwise, test fails.
wire( "TaskList" ).seeData( "tasksRemoved", true );
Verifies a Data Property does not match a specified value. Otherwise, test fails.
wire( "TaskList" ).dontSeeData( "tasksRemoved", true );
Your Wire's HTML. Simple as that. Honestly.
Wires require a template that contains the HTML to be rendered.
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.
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.
Instead of creating a .cfm template, you can define an onRender() method on your Wire.
See the Wire Lifecycle page for additional details.
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();
}
}
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>.
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.
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. 👋
<div>
<button wire:click="addTask" wire:loading.attr="disabled">
Add Task
</button>
</div>
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>
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 -->
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>
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>
Similar to ColdBox, you can relocate users using relocate.
You can redirect users in your Actions using 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
)
With CommandBox, you can start building reactive CFML apps in seconds.
Adobe ColdFusion 2018+ or Lucee 5+
ColdBox 6+
Install CommandBox.
Within the root of your project, run:
box install cbwire
If you want the latest bleeding edge, run:
box install cbwire@be
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.
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>
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
.
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.
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]()
.
You can use this as an alternative to using data
.
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.
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.
include powerful validations that you can use to validate your .
You can define constraints within your wires using this.constraints
.
can validate against the defined constraints using validate()
or validateOrFail()
.
Returns a ValidateResult
object.
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.
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
}
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>
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:
CBWIRE makes an initial request to the server to get a temporary "signed" upload URL.
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.
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 ).
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.
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.
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.
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.
<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.
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>
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 }">
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
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.
Avoid over usage, as it will lead to loading content that is not needed.
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.
In the root of your ContentBox application, install CBWIRE using . The example below installs the latest bleeding-edge version.
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.
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>
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>
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>
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:
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">
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 ){
}
Actions shouldn't return any value. Return values are ignored.
You can access data properties inside your actions directly using data
.
data = {
"task": ""
};
// Actions
function clearTasks(){
data.task = "";
}
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...
}
}
In CBWIRE, there are some "magic" actions that are usually prefixed with a "$" symbol:
$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"
};
}
CBWIRE's powerful directives allow you to listen for client side events, invoke actions, bind to data properties, and more.
The following Directives can be added to your Templates and instruct CBWIRE how and when to update the Wire.
<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.
<button wire:click="foo">...</button>
<a href="" wire:click.prevent="foo">Click here</a>
Listens for a click
event and invokes the foo
Action.
<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.
<form wire:submit.prevent="someAction">
<button type="submit">Submit Form</button>
</form>
Listeners for a submit
event on a form.
<input wire:keydown="foo" type="text">
Listens for a keyDown
event and invokes the foo
Action.
<input wire:keydown.enter="foo" type="text">
Listens for a keyDown
event when the user hits enter and invokes the foo
Action.
<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.
<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.
<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.
<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.
<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 .
<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
<div wire:init="foo"></div>
Invokes the foo
Action on your Wire immediately after it's rendered on the page.
<span wire:loading><img src="spinner.gif"></span>
Hides the HTML element by default and makes it visible when XHR requests are performed.
<div wire:loading.class="highlight"></div>
Adds the foo
class to the HTML element while XHR requests are in transit.
<div wire:loading.class.remove="highlight" class="highlight"></div>
Removes the foo
class from the HTML element while XHR requests are in transit.
<button wire:loading.attr="disabled">...</button>
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.
<div wire:dirty="foo">...</div>
<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.
<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.
<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
.
<button wire:dirty.attr="disabled" wire:target="foo">...</div>
Provides scoping for wire:loading
and wire:dirty
references, scoped to a specific Action.
<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.
<div wire:ignore.self></div>
Instructs Livewire to not update the element but DOES allow updates to any child elements when updating the DOM.
See Offline State
CBWIRE Directives sometimes offer "modifiers" to add extra functionality to an event. Here are the available modifiers that can be used with any event.
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>
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:
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'.
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!
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!
CBWIRE renders our Counter Wire template. Our counter value defaults to 0.
When a user clicks the plus button, CBWIRE detects the event and makes an XHR request to the server.
CBWIRE picks up the XHR request and invokes the increment action.
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 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!
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.
Please consider becoming one of our lovingly esteemed Patreon supporters.
CBWIRE Examples: https://github.com/grantcopley/cbwire-examples
ForgeBox: https://forgebox.io/view/cbwire
GitHub Repository: https://github.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
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.
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();
}
};
}
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();
}
}
}
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>
Everything that has a beginning has an end. - The Oracle, Matrix
When a Wire is initially loaded (before any background AJAX requests are performed), the Lifecycle Methods are executed in the following order.
onMount()
Render Computed Properties
onRender() or implicit Template rendering
For background AJAX requests, lifecycle methods are executed in this order.
onHydrate[DataProperty]()
onHydrate()
Render Computed Properties
Execute Actions
onRender() or implicit Template rendering
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.
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>
";
}
}
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;
}
}
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>
";
}
}
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.
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>
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>