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

A “Real-Time” Machine

Daniel Alvidrez
5 min readFeb 1, 2018
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

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