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...
Poll for state changes based on a specified interval without page refreshes. Hot dog!
Just as with ColdBox, you can inject your dependencies using WireBox.
Call an action immediately once your Wire is first rendered.
Prefetch state changes when the user mouses over an HTML element. Fantastico!


https://yourapp.com/search-articles?search=some+stringcomponent extends="cbwire.models.Component" {
data = {
"search": ""
};
queryString = [ "search" ];
}<div wire:poll></div>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" );
}
}<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();
}
}<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;
}
}Toggle UI elements when the user is offline or online. Groovy!
<div wire:offline><!-- Oh no, you're offline --></div><div wire:offline.class="online" class="online"></div><div wire:offline.class.remove="im-online" class="im-online"></div><div>
<input wire:model="search" type="search" placeholder="Search articles...">
</div><div wire:poll.5s> <!-- poll every 5 seconds --> </div><div wire:poll="refreshTasks"></div>component extends="cbwire.models.Component" {
function refreshTasks() {}
}<cfif args.shouldPoll>
<div wire:poll="refreshTasks"></div>
<cfelse>
<div><!-- No more polling --></div>
</cfif>component extends="cbwire.models.Component" {
property name="storage" inject="cbfs:disks:temp";
function onDIComplete(){
storage.create( "log.txt", "Dependency Injection Complete!" );
}
}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.
Alter CBWIRE's behavior with these nifty configuration options.
You can emit events from both your Wires and JavaScript. Superb!
Similar to ColdBox, you can relocate users using relocate.
Track changes to Data Properties and display changes in your UI instantly.
You can listen for livewire:load and place any JavaScript there.
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
} );
}
}<div>
<!--- Your component's template --->
<script>
document.addEventListener('livewire:load', function () {
// Your JS here.
})
</script>
</div>listeners.dirtywire:target.<div>
<input wire:dirty.class="border-red-500" wire:model.lazy="foo">
</div>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>// 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
)function someAction() {
emit( "taskAdded" );
}function someAction() {
emit( "taskAdded", [
"some task",
now()
] );
}<button wire:click="$emit( 'taskAdded' )"><script>
cbwire.emit( 'taskAdded' );
</script>component extends="cbwire.models.Component"{
listeners = {
"taskAdded": "clearCache"
};
function clearCache(){}
}<script>
cbwire.on( 'taskAdded', task => {
console.log( 'A task was added. ');
})
</script><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><!--- ./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>Wires are reactive UI elements you create. They include a powerful feature-set provided by CBWIRE.
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( ... );
}
}
}Your Wire's HTML. Simple as that. Honestly.
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
}Define reactive properties for your UI Wires with just a few lines of code.
Create dynamic properties for your UI Wires.
// 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-cbwirecbwire create Counter
// Created /wires/Counter.cfc
// Created /views/wires/counter.cfm
// Created /tests/specs/integration/wires/CounterTest.cfccbwire create name=Counter views=false
// Created /wires/Counter.cfc
// Created /tests/specs/integration/wires/CounterTest.cfccomponent 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>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.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>args.[propertyName].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" {
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>Create dynamic properties for your UI Wires.
Help website users during wait times using Loading States.
component extends="cbwire.models.Component"{
function addTask(){
sleep( 5000 ); // Optimize this code yo!
}
}<div>
<button wire:click="addTask">Add Task</button>
<div wire:loading>
Adding Task...
</div>
</div><div>
<button wire:click="addTask" wire:loading.attr="disabled">
Add Task
</button>
</div>Front-end testing of your UI Wires using a beautiful test API.
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 propertieswire( "TaskList" )
.computed( "count", function() { return 3; } ) // one property at a time
.computed( { "count" : function() { return 3; } } ); // struct of propertieswire( "TaskList" ).toggle( "showModal" );Beautifully integrate your client-side JavaScript and CBWIRE 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><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>// wires/Counter.cfc
component extends="cbwire.models.Component" {
data = {
"counter": 0
};
function increment() {
data.counter += 1;
}
}Remove full-page reloads from your applications and create single-page applications with ease using Turbo.
<input type="file" wire:model="photo">
<div wire:loading wire:target="photo">Uploading...</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" {
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>
";
}
}<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><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><!--- 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>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 );<!--- 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 }">// File: ./config/ColdBox.cfc
component{
function configure() {
moduleSettings = {
"cbwire" = {
"enableTurbo": true
}
};
}
}
npm i @hotwired/turboimport * 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>The magic sauce. Actions allow you to easily listen to page interactions and call a method on your Wires.


<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"
};
}CBWIRE's powerful directives allow you to listen for client side events, invoke actions, bind to data properties, and more.
<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">