Mojolicious-Plugin-Multiplex/ex/vue_chat.pl
use Mojolicious::Lite;
use Mojo::Pg;
die 'set MULTIPLEX_PG_URL to use this demo'
unless my $url = $ENV{MULTIPLEX_PG_URL};
plugin 'Multiplex';
helper pg => sub { state $pg = Mojo::Pg->new($url) };
get '/' => 'chat';
websocket '/channel' => sub {
my $c = shift;
$c->inactivity_timeout(3600);
my $pubsub = $c->pg->pubsub;
my $multiplex = $c->multiplex;
#NOTE these implementation respond when already subscribe,
# they could also send error if so desired
my %channels;
$multiplex->on(subscribe => sub {
my ($multiplex, $channel) = @_;
unless(exists $channels{$channel}) {
$channels{$channel} = $pubsub->listen($channel => sub {
my ($pubsub, $payload) = @_;
$c->multiplex->send($channel => $payload);
});
}
$multiplex->send_status($channel, 1);
});
$multiplex->on(message => sub {
my (undef, $channel, $payload) = @_;
$pubsub->notify($channel => $payload);
});
$multiplex->on(unsubscribe => sub {
my (undef, $channel) = @_;
if(my $cb = delete $channels{$channel}) {
$pubsub->unlisten($channel => $cb);
}
$multiplex->send_status($channel, 0);
});
$multiplex->on(finish => sub {
$pubsub->unlisten($_ => $channels{$_}) for keys %channels;
});
};
app->start;
__DATA__
@@ chat.html.ep
<!DOCTYPE html>
<html>
<head>
%= stylesheet 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css'
</head>
<body>
<div id="chat" class="container">
<div class="row">
<div class="col-md-3">
<ul class="list-group">
<li v-for="c in channels" class="list-group-item" @click.prevent="select_channel(c)">
<span class="badge">{{c.unread}}</span>
<button type="button" @click.prevent="remove_channel(c)">×</button>
{{c.name}}
</li>
<li class="list-group-item">
Add Channel: <form @submit.prevent="add_channel"><input v-model="new_channel"></form>
</li>
</ul>
</div>
<div class="col-md-9">
<div class="page-header" v-if="current.name"><h1>Chatting on {{current.name}}</h1></div>
<div id="log"><p v-for="m in current.messages">{{m.username}}: {{m.message}}</p></div>
</div>
<div class="navbar-fixed-bottom">
<div class="container">
<div class="col-md-3">
<div class="input-group">
<div class="input-group-addon">Username</div>
<input class="form-control" v-model="username">
</div>
</div>
<div class="col-md-9">
<form class="form" @submit.prevent="send">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon">Send</div>
<input class="form-control" v-model="message">
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
%= javascript 'https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.20/vue.js'
%= javascript '/websocket_multiplex.js'
<script>
var vm = new Vue({
el: '#chat',
data: {
username: '',
new_channel: '',
url: '<%= url_for('channel')->to_abs %>',
channels: [],
current: {},
message: '',
},
computed: {
ws: function() {
var ws = new WebSocket(this.url);
ws.onopen = function() { console.log('websocket open') };
return ws;
},
multiplexer: function() { return new WebSocketMultiplex(this.ws) },
},
methods: {
add_channel: function() {
var name = this.new_channel;
this.new_channel = '';
var channel = {'name': name, messages: [], unread: 0};
var socket = this.multiplexer.channel(name);
var self = this;
socket.onmessage = function (e) {
channel.messages.push(JSON.parse(e.data));
if ( channel !== self.current ) { channel.unread++ }
};
channel.socket = socket;
this.channels.push(channel);
this.current = channel;
},
remove_channel: function(channel) {
channel.socket.close();
this.channels.$remove(channel);
},
select_channel: function(channel) {
this.current = channel;
channel.unread = 0;
},
send: function() {
this.current.socket.send(JSON.stringify({username: this.username, message: this.message}));
this.message = '';
},
},
ready: function() { this.ws },
});
</script>
</body>
</html>