Hello fellow DEVs,
In my previous post, we implemented 80% of our chat backend.
This is the link to the post, in case you missed:
https://dev.to/joan41868/java-chat-app-backend-53k3
Today, we'll continue with implementing basic frontend, as well as enhancing our backend to remember our users. Let's GO!
Prerequisite - Node.js 10+ installed, with NPM/yarn
Install vue-cli with:
npm i -g @vue/cli # you may need sudo under linux
OR
yarn global add @vue/cli
Then, go on and create a new vue project with:
vue create {project_name}
You will be prompted to select dependencies manually/automatically. It's up to you which dependencies you choose to have. I've chosen:
- Babel
- Vue Router
- Vuex
You should see this as project structure:
Open VSCode in the project folder, and type in the terminal:
npm run serve
This will start your frontend application on port 8080. (Please note that Spring's default port is 8080 too, so your vue app may start at port 8081).
Lets add our dependencies:
npm i axios bootstrap bootstrap-vue
npm i sockjs-client webstomp-client
We need to define several components - our router and our chat page. We will start with the chat page, as it's more complex in nature.
Before anything else, this is the main.js file. Nothing complex, just vue+bootstrap setup.
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
// Import Bootstrap an BootstrapVue CSS files (order is important)
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
// Make BootstrapVue available throughout your project
Vue.use(BootstrapVue)
// Optionally install the BootstrapVue icon components plugin
Vue.use(IconsPlugin)
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
We will start off by defining the methods in the chat page, as follows:
methods: {
goBack(){
this.$router.push({name: 'Home'});
},
addMessage(message) {
if (this.messages.length > 5) {
this.messages.shift();
}
this.messages.push(message);
this.scrollOnNewMessage();
},
async sendInitialConnectionRequest() {
const ax = axios.default;
const ipifyUrl = 'https://api.ipify.org?format=json';
const ipData = (await ax.get(ipifyUrl)).data;
const {
ip
} = ipData;
const checkUrl = this.beUrl + '/check/' + ip;
// data should contain IP
const rsp = await ax.get(checkUrl);
const {
data
} = rsp;
if (data.ipAddressHash && data.username) {
this.username = data.username;
} else {
this.username = prompt("Enter a username");
if (this.username) {
const registerUrl = this.beUrl + "/create";
const requestData = {
ipAddress: ip,
username: this.username
};
await ax.post(registerUrl, requestData);
} else {
await this.sendInitialConnectionRequest();
}
}
},
subscribeToChat() {
if (this.username && this.username !== '') {
this.stompClient.subscribe("/topic/messages/" + this.username, (data) => {
const {
senderUsername,
recipientUsername,
content
} = JSON.parse(data.body);
const message = {
senderUsername,
recipientUsername,
content,
};
this.addMessage(message);
});
}
},
connectToChatServer() {
this.connectedToServer = false;
this.socket = new SockJs(this.beUrl + "/chat-app");
this.stompClient = StompClient.over(this.socket, {
debug: false
});
this.stompClient.connect({}, (frame) => {
this.connectedToServer = true;
this.subscribeToChat();
});
},
sendMessage(e) {
e.preventDefault(); // prevent default form submission
const message = {
senderUsername: this.username,
recipientUsername: this.recipientUsername,
content: this.messageContent,
};
if (message.content.length > 0) {
this.stompClient.send("/topic/messages/" + message.recipientUsername, JSON.stringify(message), {});
this.addMessage(message); // add message to currently shown messages
this.messageContent = "";
}
},
scrollOnNewMessage(){
const msgContainerHtmlNode = document.getElementById("message-container");
msgContainerHtmlNode.scrollTop = msgContainerHtmlNode.scrollHeight;
}
}
And here are the missing variables you probably noticed:
data() {
return {
connectedToServer: false,
messages: [],
showInfo: Boolean,
username: String,
messageContent: "",
recipientUsername: "",
beUrl: 'http://localhost:8080' //'https://java-chat-backend.herokuapp.com'
}
},
This is our chat page markup:
<template>
<div class="home bg-dark">
<show-info-button></show-info-button>
<go-home-button></go-home-button>
<p style="color: white; font-size: 22px; color: #b491c8;">Your are: {{username}}</p>
<b-form @submit="sendMessage"
style="display: flex; flex-direction: column; justify-content: space-between; margin: auto; width: 75%;">
<b-form-input id="recipient" v-model="recipientUsername" type="text" placeholder="recipient..."
class="bg-dark" style="margin-top: 5px; margin-bottom: 5px; color: #b491c8;" />
<div id="message-container" class="chatmessagecontainer" v-show="messages.length > 0">
<span v-for="message in messages" v-bind:key="message.content + Math.random().toString()">
<message :content="message.content" :senderUsername="message.senderUsername"></message>
</span>
</div>
<div class="chat-controller">
<b-form-input id="messageContent" v-model="messageContent" type="text" placeholder="Your message.."
class="bg-dark" style="margin-top: 5px; margin-bottom: 10px; color: #b491c8;" />
<span
style="display: flex; flex-direction: row; justify-content: space-between; margin-top: 5px; margin: auto;">
<b-button type="submit" variant="outline-success" @click="sendMessage">
<b-icon icon="chat-fill"></b-icon> Send
</b-button>
<b-button variant="outline-danger" @click="()=>{messages=[]}">
<b-icon icon="trash"></b-icon> Clear
</b-button>
</span>
</div>
</b-form>
</div>
</template>
And the scoped style:
.chatmessagecontainer {
display: flex;
flex-direction: column;
justify-content: space-evenly;
max-height: 320px;
overflow-x: none;
overflow-y: scroll;
border: 1px solid black;
border-radius: 8px;
margin: auto;
margin-top: 10px;
width: 100%;
padding: 4px 4px 4px 4px;
}
.chat-controller {
margin: auto;
width: 75%;
position: absolute;
left: 0;
right: 0;
bottom: 5px;
}
button {
font-size: 15px;
}
input {
font-size: 16px;
color: #b491c8;
}
And the router file:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Chat from '../views/Chat.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/chat',
name: 'Chat',
component: Chat
},
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
And finally, the three missing components you definetely noticed:
Message:
<template>
<div class="chatmessage">
<p class="chat-message-username"><b>{{senderUsername}}</b></p>
<p class="chat-message-content">{{content}}</p>
</div>
</template>
<script>
export default {
props:{
content: String,
senderUsername: String,
createdAt: Date
}
}
</script>
<style >
.chatmessage{
background-color: #212121;
color: white;
border-radius: 9px;
max-width: 250px;
margin: auto;
font-size: 15px;
max-height: 60px;
}
.chat-message-content{
font-size: 14px;
margin-top: 0;
padding: 0;
}
.chat-message-username{
font-size: 15px;
font-family: monospace;
}
</style>
GoHomeButton
<template>
<b-icon icon="arrow-left" style="position: absolute; top: 5px; left: 5px; font-size: 33px;" class="h4 text-danger"
@click="goBack()"></b-icon>
</template>
<script>
export default {
methods:{
goBack(){
this.$router.push("/");
}
}
}
</script>
<style>
</style>
ShowInfoButton
<template>
<div>
<b-icon icon="question" style="position: absolute; top: 5px; right: 5px; font-size: 33px;"
class="h4 text-success" v-b-modal.modal-1></b-icon>
<b-modal id="modal-1" title="Safemessage" body-bg-variant="dark" header-bg-variant="dark"
footer-bg-variant="dark" body-text-variant="light" header-text-variant="light" :cancel-disabled="true">
<p>A secure chat aimed at maximum privacy.</p>
<p>No cookies.</p>
<p>No trackers.</p>
<p>No database for messages.</p>
<router-link to="/donate">Support the author :)</router-link>
</b-modal>
</div>
</template>
This is all for this post, as it got pretty long. In the next one, the 3rd part, we will enhance our backend so it can accept the initialConnectionRequest sent from our frontend.
Thank you for dropping by, and have a nice day :)
Top comments (0)