Vue Chat - Frontend
Published: 2021-03-02 09:56:28

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:
```sh
 npm i -g @vue/cli # you may need sudo under linux 
```
OR
```sh
yarn global add @vue/cli 
```

Then, go on and create a new vue project with:

```sh
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:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6lb320955f7fondvjrrp.png)


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:
```sh
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.

```js

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: 
```js

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:
```js

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:

```vue

<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:

```css

	.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:

```js

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:
```js

<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
```js

<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
```vue

<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 :)