Pusher + Vue + Laravel = Let’s Do It!
A “Real-Time” Machine
npm install axios (laravel default)
npm install vue (laravel default)
npm install pusher-js
npm install collect.js
Vue Component Scaffolding
You can think of this component as our central-storage for pusher. If you’re using VueX you can adapt this to your $store.
**Don’t forget to load the component in your main Vue instance.
export default{
name:'pusher-events',
props:['api_key','api_cluster', 'channel_data'],
data() {
return {
channels:{},
form:{} //for method testing
}
},
beforeMount(){
this.initPusher()
this.bindConnections()
},
methods: {
//...
},
}
Component Tag
Place the component tag in a template:
*We’ll be using simple sessions for this demo.
<pusher-events
api_key="{{env('PUSHER_APP_KEY')}}"
api_cluster="{{env('PUSHER_APP_CLUSTER')}}"
:channel_data="{
test: {{ json_encode(session()->get('pusher-test', [])) }}
}">
</pusher-events>
Add Method: initPusher()
/** Init Pusher **/
initPusher(){
this.client = new Pusher(this.api_key,{
cluster: this.api_cluster,
encrypted: true
});
},
Add Method: bindConnections()
/** Bind Connections **/
bindConnections() {
let vm = this
const subscriptionChannels = [{
name: 'test',
events: [{
name: 'App\\Events\\TestPublish',
callback: vm.pushData
},
{
name: 'App\\Events\\TestRemove',
callback: vm.removeData
},
{
name: 'App\\Events\\TestUpdate',
callback: vm.updateData
}
]
}] //Loop Channels
for(let channel of subscriptionChannels){
//...
}
},
Add Method: bindConnections() — Continued
//Loop Channels
for(let channel of subscriptionChannels){...//Load Channel Data from Prop
let channelData = []
if(this.channel_data.hasOwnProperty(channel.name)){
channelData = this.channel_data[channel.name]
}
//Init Channel
this.$set(this.channels, channel.name, {
channel: this.client.subscribe(channel.name),
data: channelData,
})//Loop Channel Events
for(let event of channel.events){
//Bind Channel Event
this.channels[channel.name].channel.bind(event.name, (data) => {
//Fire Event Callback
event.callback(channel.name, data)
});
}
...}
What did we just build? — A home for our channels & data!
Data Methods
Now that we have a schema to hold our realtime channels and data. We can add methods to perform operations that will reflect the state of our database / storage when we fire events.
If you’re using VueX you can use your $store.
Add Method: pushData()
When a new object is stored, the pushData() callback will add the new object to our channel’s data array which forces Vue to update the page.
/** Push Object to Connection's DataSet **/
pushData(name, data) {
this.channels[name].data.push(data.object)
console.info(name, data.object, 'added')
},
Add Method: removeData()
When an object is removed from our server’s state, the removeData() callback will remove the object from our channel’s data array.
/** Remove Object from Connection's DataSet **/
removeData(name, data) {
let dataSet = this.channels[name].data
let collection = collect(dataSet)
let item = collection.where('id', data.object.id).first()
if(item){
dataSet.splice(dataSet.indexOf(item), 1)
console.info(name, data.object, 'removed')
}
},
Add Method: updateData()
When an object is updated in our server’s state, the updateData() callback will splice a new version of the object to our channel’s data array.
/** Update Object in Connection's DataSet **/
updateData(name, data) {
let dataSet = this.channels[name].data
let collection = collect(dataSet)
let item = collection.where('id', data.object.id).first()
if(item) {
dataSet.splice(dataSet.indexOf(item), 1, data.object)
console.info(name, data.object, 'updated')
}
},
Add Method: submitForm() — (demo)
We will use a basic form to test our methods.
/** Submit Form (demo) **/
submitForm(){
axios
.post('/app-events', this.form)
.then((response)=>{
console.info(response.data)
})
.catch((error)=>{
console.error(error.response.data)
})
}
Template Time!
You can include a basic form to test our events.
**Be sure to wrap everything is a single div or Vue will be upset!
<div v-for="(object, index) in channels.test.data" :key="index">
{{ object }}
</div><form @submit.prevent="submitForm">
<input type="text" v-model="form.content"/>
<select v-model="form.id" v-if="form.event !== 'test-publish'">
<option v-for="object in channels.test.data" :value="object.id">
{{object.id}}
</option>
</select>
<select v-model="form.event">
<option value="test-publish">Add</option>
<option value="test-update">Update</option>
<option value="test-remove">Remove</option>
</select>
<button type="submit">
Submit
</button>
</form>
A Route Specifically for Events
To get things rolling quickly let’s use a basic route to fire our events. We can be ambiguous about our data by using an anonymous object naming scheme.
With this route we can make a request to fire any event in our application with an anonymous object that should be handled by our event class instead of using a controller. — Why instantiate another class if we don’t need to!
Route::post('app-events', function(){
$event = '\\App\\Events\\'.camel_case(request()->get('event'));
if(class_exists($event)){
event(app()->makeWith($event, array(
'object' => (object) request()->all()
)));
return response(request()->all(), 200);
}
return response('Event does not exist.', 404);
});
Add the TestPublish Event
php artisan make:event TestPublish
We’ll use our session manager to store our objects for this demonstration. *Replace your new class with the following:
class TestPublish implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $object;
/**
* Create a new event instance.
* @param $object
* @return void
*/
public function __construct($object)
{
$this->object = $object;
session()->push('pusher-test', $this->object);
}
/**
* Get the channels the event should broadcast on.
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return ['test'];
}
}
Add the TestRemove Event
php artisan make:event TestRemove
*Replace your new class with the following:
class TestRemove implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $object;
/**
* Create a new event instance.
* @param $object
* @return void
*/
public function __construct($object)
{
$this->object = $object;
session()->put('pusher-test', collect(session()->get('pusher-test'))->reject(function ($storedObject) {
return $storedObject->id === $this->object->id;
}));
}
/**
* Get the channels the event should broadcast on.
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return ['test'];
}
}
Add the TestUpdate Event
php artisan make:event TestUpdate
*Replace your new class with the following:
class TestUpdate implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $object;
/**
* Create a new event instance.
* @param $object
* @return void
*/
public function __construct($object)
{
$this->object = $object;
session()->put('pusher-test', collect(session()->get('pusher-test'))->map(function ($storedObject) {
if($storedObject->id == $this->object->id){
return $this->object;
}
return $storedObject;
}));
}
/**
* Get the channels the event should broadcast on.
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return ['test'];
}
}
Wrap Up: Video
**Let me know if you find any bugs in this demonstration. :)