Pusher + Vue + Laravel = Let’s Do It!

A “Real-Time” Machine

Daniel Alvidrez
Feb 1, 2018 · 5 min read
Google Drawing of what we’re building :)
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!

Vue Component Data Tree via: https://github.com/vuejs/vue-devtools

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

https://vimeo.com/253807963

**Let me know if you find any bugs in this demonstration. :)

Daniel Alvidrez

Written by

Full Stack Developer — Community Moderator @ Laravel PHP Framework Facebook Group

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade