curl -L https://cpanmin.us | perl - -M https://cpan.metacpan.org -n Mojolicious
use Mojolicious::Lite -signatures;
get '/' => 'chat';
my %subscribers;
websocket '/channel' => sub ($c) {
$c->inactivity_timeout(3600);
# Add controller to hash of subscribers
$subscribers{$c} = $c;
# Forward messages from the browser to all subscribers
$c->on(message => sub ($c, $message) {
$_->send($message) for values %subscribers;
});
# Remove controller from subscribers on close
$c->on(finish => sub ($c, @) { delete $subscribers{$c} });
};
app->start;
__DATA__
@@ chat.html.ep
<form onsubmit="sendChat(this.children[0]); return false"><input></form>
<div id="log"></div>
<script>
var ws = new WebSocket('<%= url_for('channel')->to_abs %>');
ws.onmessage = function (e) {
document.getElementById('log').innerHTML += '<p>' + e.data + '</p>';
};
function sendChat(input) { ws.send(input.value); input.value = '' }
</script>
ex/mojo_basic_chat.pl
websocket '/channel' => sub ($c) {
$c->inactivity_timeout(3600);
# Add controller to hash of subscribers
$subscribers{$c} = $c;
# Forward messages from the browser to all subscribers
$c->on(message => sub ($c, $message) {
$_->send($message) for values %subscribers;
});
# Remove controller from subscribers on close
$c->on(finish => sub ($c, @) { delete $subscribers{$c} });
};
ex/mojo_basic_chat.pl
<form onsubmit="sendChat(this.children[0]); return false"><input></form>
<div id="log"></div>
<script>
var ws = new WebSocket('<%= url_for('channel')->to_abs %>');
ws.onmessage = function (e) {
document.getElementById('log').innerHTML += '<p>' + e.data + '</p>';
};
function sendChat(input) { ws.send(input.value); input.value = '' }
</script>
ex/mojo_basic_chat.pl
use Mojo::Base -strict;
use Test::More;
use Test::Mojo;
require './ex/mojo_basic_chat.pl';
my $t = Test::Mojo->new;
$t->get_ok('/')->status_is(200);
$t->websocket_ok('/channel')
->send_ok('is this thing on?')
->message_ok
->message_is('is this thing on?')
->finish_ok;
done_testing;
ex/mojo_basic_chat.t
websocket '/channel' => sub ($c) {
$c->inactivity_timeout(3600);
# Add controller to hash of subscribers
$subscribers{$c} = $c;
# Forward messages from the browser to all subscribers
$c->on(message => sub ($c, $message) {
$_->send($message) for values %subscribers;
});
# Remove controller from subscribers on close
$c->on(finish => sub ($c, @) { delete $subscribers{$c} });
};
ex/mojo_basic_chat.pl
helper pg => sub { state $pg = Mojo::Pg->new('postgresql://test:test@/test') };
websocket '/channel' => sub ($c) {
$c->inactivity_timeout(3600);
# Forward messages from the browser to PostgreSQL
$c->on(message => sub ($c, $message) {
$c->pg->pubsub->notify(mojochat => $message);
});
# Forward messages from PostgreSQL to the browser
my $cb = sub ($pubsub, $message) { $c->send($message) };
$c->pg->pubsub->listen(mojochat => $cb);
# Remove callback from PG listeners on close
$c->on(finish => sub ($c, @) {
$c->pg->pubsub->unlisten(mojochat => $cb);
});
};
ex/mojo_pg_chat.pl
<form onsubmit="sendChat(this.children[0]); return false"><input></form>
<div id="log"></div>
<script>
var ws = new WebSocket('<%= url_for('channel')->to_abs %>');
ws.onmessage = function (e) {
document.getElementById('log').innerHTML += '<p>' + e.data + '</p>';
};
function sendChat(input) { ws.send(input.value); input.value = '' }
</script>
ex/mojo_basic_chat.pl
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.5/vue.js"></script>
<div id="chat">
<form @submit.prevent="send"><input v-model="current"></form>
<div id="log"><p v-for="m in messages">{{m}}</p></div>
</div>
<script>
var ws = new WebSocket('<%= url_for('channel')->to_abs %>');
var vm = new Vue({
el: '#chat',
data: {
current: '',
messages: [],
},
methods: {
send: function() {
ws.send(this.current);
this.current = '';
},
},
});
ws.onmessage = function (e) { vm.messages.push(e.data) };
</script>
ex/vue_chat.pl
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.5/vue.js"></script>
<div id="chat">
Username: <input v-model="username"><br>
Send: <input @keydown.enter="send" v-model="current"><br>
<div id="log">
<p v-for="m in messages">{{m.username}}: {{m.message}}</p>
</div>
</div>
<script>
var ws = new WebSocket('<%= url_for('channel')->to_abs %>');
var vm = new Vue({
el: '#chat',
data: {
current: '',
messages: [],
username: '',
},
methods: {
send: function() {
var data = {username: this.username, message: this.current};
ws.send(JSON.stringify(data));
this.current = '';
},
},
});
ws.onmessage = function (e) { vm.messages.push(JSON.parse(e.data)) };
</script>
ex/vue_chat_user.pl
use Mojolicious::Lite -signatures;
use Mojo::Pg;
helper pg => sub { state $pg = Mojo::Pg->new('postgresql://test:test@/test') };
get '/' => 'chat';
websocket '/channel' => sub ($c) {
$c->inactivity_timeout(3600);
# Forward messages from the browser to PostgreSQL
$c->on(message => sub ($c, $message) {
$c->pg->pubsub->notify(mojochat => $message);
});
# Forward messages from PostgreSQL to the browser
my $cb = sub ($pubsub, $message) { $c->send($message) };
$c->pg->pubsub->listen(mojochat => $cb);
# Remove callback from PG listeners on close
$c->on(finish => sub ($c, @) {
$c->pg->pubsub->unlisten(mojochat => $cb);
});
};
app->start;
__DATA__
@@ chat.html.ep
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.5/vue.js"></script>
<div id="chat">
Username: <input v-model="username"><br>
Send: <input @keydown.enter="send" v-model="current"><br>
<div id="log">
<p v-for="m in messages">{{m.username}}: {{m.message}}</p>
</div>
</div>
<script>
var ws = new WebSocket('<%= url_for('channel')->to_abs %>');
var vm = new Vue({
el: '#chat',
data: {
current: '',
messages: [],
username: '',
},
methods: {
send: function() {
var data = {username: this.username, message: this.current};
ws.send(JSON.stringify(data));
this.current = '';
},
},
});
ws.onmessage = function (e) { vm.messages.push(JSON.parse(e.data)) };
</script>
ex/vue_chat_user.pl
(generated using David A. Wheeler's 'SLOCCount')
Vue.component('chat-msg', {
template: '<p>{{username}}: {{message}}</p>',
props: {
username: { type: String, required: true },
message: { type: String, default: '' },
},
});
ex/vue_chat_comp.pl
Vue.component('chat-entry', {
template: '<input @keydown.enter="message" v-model="current">',
data: function() { return { current: '' } },
methods: {
message: function() {
this.$emit('message', this.current);
this.current = '';
},
},
});
ex/vue_chat_comp.pl
<div id="chat">
Username: <input v-model="username"><br>
Send: <chat-entry @message="send"></chat-entry><br>
<div id="log">
<chat-msg v-for="m in messages" :username="m.username" :message="m.message"></chat-msg>
</div>
</div>
ex/vue_chat_comp.pl
var vm = new Vue({
el: '#chat',
data: { messages: [], username: '', ws: null },
methods: {
connect: function() {
var self = this;
self.ws = new WebSocket('<%= url_for('channel')->to_abs %>');
self.ws.onmessage = function (e) { self.messages.push(JSON.parse(e.data)) };
},
send: function(message) {
var data = {username: this.username, message: message};
this.ws.send(JSON.stringify(data));
},
},
created: function() { this.connect() },
});
ex/vue_chat_comp.pl
use Mojolicious::Lite -signatures;
use Mojo::Pg;
helper pg => sub { state $pg = Mojo::Pg->new('postgresql://test:test@/test') };
get '/' => 'chat';
websocket '/channel' => sub ($c) {
$c->inactivity_timeout(3600);
# Forward messages from the browser to PostgreSQL
$c->on(message => sub ($c, $message) {
$c->pg->pubsub->notify(mojochat => $message);
});
# Forward messages from PostgreSQL to the browser
my $cb = sub ($pubsub, $message) { $c->send($message) };
$c->pg->pubsub->listen(mojochat => $cb);
# Remove callback from PG listeners on close
$c->on(finish => sub ($c, @) {
$c->pg->pubsub->unlisten(mojochat => $cb);
});
};
app->start;
__DATA__
@@ chat.html.ep
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.5/vue.js"></script>
<div id="chat">
Username: <input v-model="username"><br>
Send: <chat-entry @message="send"></chat-entry><br>
<div id="log">
<chat-msg v-for="m in messages" :username="m.username" :message="m.message"></chat-msg>
</div>
</div>
<script>
Vue.component('chat-entry', {
template: '<input @keydown.enter="message" v-model="current">',
data: function() { return { current: '' } },
methods: {
message: function() {
this.$emit('message', this.current);
this.current = '';
},
},
});
Vue.component('chat-msg', {
template: '<p>{{username}}: {{message}}</p>',
props: {
username: { type: String, required: true },
message: { type: String, default: '' },
},
});
var vm = new Vue({
el: '#chat',
data: { messages: [], username: '', ws: null },
methods: {
connect: function() {
var self = this;
self.ws = new WebSocket('<%= url_for('channel')->to_abs %>');
self.ws.onmessage = function (e) { self.messages.push(JSON.parse(e.data)) };
},
send: function(message) {
var data = {username: this.username, message: message};
this.ws.send(JSON.stringify(data));
},
},
created: function() { this.connect() },
});
</script>
ex/vue_chat_comp.pl