# How to Build a Chat App
![](/projects/chat-app/guideAssets/final.png)
> - 🧑💻 [You will build this](https://app.qoom.io/~/projects/chat-app/chat)
> - ▶️ [Tutorial Video](https://youtu.be/tnsqo4hwutg)
> - 🗂️ Source Files
> - [index.html](https://app.qoom.io/edit/projects/chat-app/chat/index.html)
## What you will get from this workshop
1. A great [starting app](#starterapp) you can use to make your own private chat application
2. A [list of resources](#listofresources) for further help and continued learning
## About the project
In this workshop you will learn how to build and customize a private chat application
While building this app you will learn how to do the following:
- HTML
- **Mobile Layout** ⇒ Creating web app that is laid out for a mobile device
- CSS
- **Flex Box** ⇒ More practice using flex box to make sure things are centered inside of elemets.
- **Grid System** ⇒ More practice using the grid system to make sure things are laided out aesthetically
- Javascript:
- **Importing 3rd party libraries** ⇒ Building off of tools built from others so that you can focus on building what you need
- **Sockets** Using sockets to communicate with other people using other browsers on other devices
## Creating your account
1. Go to <https://www.qoom.io>
2. Click the `Sign Up` button
3. Follow the instructions
***
## `HTML`: Creating our Web App Layout
Copy the following HTML:
```html
<body>
<header>
<h1>🔒 Private Chat </h1>
</header>
<main>
<div class='you'>Welcome!</div>
<div class='me'></div>
</main>
<footer>
<textarea placeholder='Type your message here...'></textarea>
<button>Send</button>
</footer>
</body>
```
In this HTML page we used the `Semantic` elements to describe our layout. The `main` section will hold the messages and the `footer` section will be where the user enters their messages.
> **Exercise**:
> - Change the text in the `h1` element.
Adding a `title` element so that your app has an title in the tab:
```html
<head>
<title>🔒 Private Chat</title>
</head>
```
***
## `CSS`: Styling our Web App
Now lets use the `Flex` and `Grid` Systems to make everything lay out nicely
```html
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css2?family=Inconsolata&family=Fredericka+the+Great&family=Mali&display=swap" rel="stylesheet">
<title>Private Chat</title>
<style>
* {
box-sizing:border-box;
margin:0;
padding:0;
}
body {
display:grid;
grid-template-rows: 64px auto 100px;
grid-template-columns: auto 600px auto;
width:100vw;
height:100vh;
overflow: hidden;
font-family: system-ui, san-serif;
background-color: #fcd411;
}
header {
grid-column-start: 2;
grid-column-end: 3;
text-align:center;
display:flex;
align-items:center;
justify-content:center;
background-color: #2e2e30;
color: #fcd411;
}
header > h1 {
font-family: 'Fredericka the Great', cursive;
font-size: 2em;
}
main {
grid-column-start: 2;
grid-column-end: 3;
list-style:none;
padding: 24px;
display:flex;
flex-direction:column;
align-items:center;
justify-content:flex-start;
background-color: white;
overflow-y: auto;
overflow-x: hidden;
}
main > div {
width: 100%;
font-size: 18px;
margin: 8px 0;
}
main > div.me {
font-family: 'Mali', cursive;
text-align: right;
}
main > div.you {
font-family: 'Inconsolata', monospace;
text-align:left;
}
footer {
grid-column-start: 2;
grid-column-end: 3;
display:grid;
grid-template-columns: auto 80px;
background-color: #fcd411;
}
footer > textarea {
font-family: 'Mali', cursive;
font-size: 18px;
padding: 8px 16px;
resize: none;
}
footer > button {
font-family: 'Fredericka the Great', cursive;
font-size: 20px;
background-color: #fcd411;
}
@media only screen and (max-width: 768px) {
body {
height: calc(100vh - 114px);
grid-template-columns: auto 100% auto;
}
}
</style>
</head>
```
> **Exercise**:
> - Change the fonts using Google Fonts <https://fonts.google.com/>
> - Change the colors
***
## `Javascript`: Creating a socket connection with other browsers
Since we are using `sockets` we will need to import the library that makes this easy. Please add the following code at the bottom of the `head` element:
```html
<script src='/libs/socketio.js'></script>
```
Then we can initialize the socket connections using the following function:
```html
<script src='/libs/socketio.js'></script>
<script type='module'>
// Creating a socket variable to talk to our server with
const socket = io('/tunnel');
async function initializeSockets() {
// Telling our webserver to create a `chat` channel to talk with other browsers with
const res = await fetch('/tunnel/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
events: ['chat']
})
});
// Waiting for our server to respond
const resp = await res.json();
// Handle any errors from our server
if(resp.error) return alert(resp.error)
// Define what to do when someone sends us a message
socket.on('chatresponse', function(data) {
});
// Define what to do when someone joins the chat room
socket.on('userconnected', (socketId) => {
console.log('userconnected: ' + socketId);
});
// Define what to do when someone leaves the chat room
socket.on('userdisconnected', (socketId) => {
console.log('disconnected: ' + socketId);
})
}
// Initializing the socket connection
initializeSockets();
</script>
```
> **Exercise**:
> - Open up the webpage you created and open up `Dev Tools` by right clicking and selecting `Inspect`
> - Send the webpage to someone else
> - See in the `console` when someone joins
***
## `Javascript`: Asking for the name of the person joining the chat
In the `script` tag created above add the following code at the top:
```javascript
// Checks `localStorage` if the person name is saved
let name = localStorage.getItem('name');
// If no name is found, ask the user for their name and save it
if(!name) {
name = prompt("What is your name?")
localStorage.setItem('name', name);
}
```
> **Exercise**:
> - Test it
> - Go to the `storage` tab in the `Dev Tools` and see the data saved in `localStorage`
***
## `Javascript`: Displaying the chat messages
At the bottom of the `script` tag created above add the following code:
```javascript
// Putting all the elements into variables
const $button = document.querySelector('button');
const $textarea = document.querySelector('textarea');
const $main = document.querySelector('main');
function sendMessage() {
// Letting all the other browsers know what you typed
socket.emit('chat', {name, text: $textarea.value});
// Displaying the message into a new div and adding it to the main element
const div = document.createElement('div');
div.className = 'me';
div.innerHTML = `<b>${name}:</b> <span>${$textarea.value}</span>`;
$main.appendChild(div);
$textarea.value = '';
// Automatically scroll to the bottom of the message
$main.scrollTo(0,$main.scrollHeight);
}
// Sending the message when the button is clicked
$button.addEventListener('click', sendMessage);
// Moving the cursor to the textbox so the user can start typing
$textarea.focus();
```
Finally we have to display the message that someone sent to us:
```javascript
socket.on('chatresponse', function(data) {
const div = document.createElement('div');
div.className = 'you';
div.innerHTML = `<b>${data.data.name}:</b> <span>${data.data.text}</span>`;
$main.appendChild(div);
$main.scrollTo(0,$main.scrollHeight);
});
```
> **Exercise**:
> - Remove the hard coded chat messages on top
> - Test your chat application
> - To create different rooms just add `?ROOM_NAME` to the end of the url. `ROOM_NAME` is what ever you want to call it.
> - Send images or videos by just entering the `html` for that image or video. For example:
- `<img src="https://media.tenor.co/images/0872d90502401d2260bf8704ae77a2a9/tenor.gif">` will send a gif of a sleepy minion
- `<iframe width="560" height="315" src="https://www.youtube.com/embed/5uNujDrsEMQ" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>` will send a youtube video
***
<h2 id='starterapp'>The final product</h2>
```html
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css2?family=Inconsolata&family=Fredericka+the+Great&family=Mali&display=swap" rel="stylesheet">
<title>Private Chat</title>
<style>
* {
box-sizing:border-box;
margin:0;
padding:0;
}
body {
display:grid;
grid-template-rows: 64px auto 100px;
grid-template-columns: auto 600px auto;
width:100vw;
height:100vh;
overflow: hidden;
font-family: system-ui, san-serif;
background-color: #fcd411;
}
header {
grid-column-start: 2;
grid-column-end: 3;
text-align:center;
display:flex;
align-items:center;
justify-content:center;
background-color: #2e2e30;
color: #fcd411;
}
header > h1 {
font-family: 'Fredericka the Great', cursive;
font-size: 2em;
}
main {
grid-column-start: 2;
grid-column-end: 3;
list-style:none;
padding: 24px;
display:flex;
flex-direction:column;
align-items:center;
justify-content:flex-start;
background-color: white;
overflow-y: auto;
overflow-x: hidden;
}
main > div {
width: 100%;
font-size: 18px;
margin: 8px 0;
}
main > div.me {
font-family: 'Mali', cursive;
text-align: right;
}
main > div.you {
font-family: 'Inconsolata', monospace;
text-align:left;
}
footer {
grid-column-start: 2;
grid-column-end: 3;
display:grid;
grid-template-columns: auto 80px;
background-color: #fcd411;
}
footer > textarea {
font-family: 'Mali', cursive;
font-size: 18px;
padding: 8px 16px;
resize: none;
}
footer > button {
font-family: 'Fredericka the Great', cursive;
font-size: 20px;
background-color: #fcd411;
}
@media only screen and (max-width: 768px) {
body {
height: calc(100vh - 114px);
grid-template-columns: auto 100% auto;
}
}
</style>
<script src='/libs/socketio.js'></script>
<script type='module'>
// GET THE PERSON'S NAME
let name = localStorage.getItem('name');
if(!name) {
name = prompt("What is your name?")
localStorage.setItem('name', name);
}
// REGISTER COMMUNICATIONS
const socket = io('/tunnel');
async function initializeSockets() {
const res = await fetch('/tunnel/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
events: ['chat']
})
});
const resp = await res.json();
if(resp.error) return alert(resp.error)
socket.on('chatresponse', function(data) {
const div = document.createElement('div');
div.className = 'you';
div.innerHTML = `<b>${data.data.name}:</b> <span>${data.data.text}</span>`;
$main.appendChild(div);
$main.scrollTo(0,$main.scrollHeight);
});
socket.on('userconnected', (socketId) => {
console.log('userconnected: ' + socketId);
});
socket.on('userdisconnected', (socketId) => {
console.log('disconnected: ' + socketId);
})
}
initializeSockets();
// SENDING CHAT MESSAGE
const $button = document.querySelector('button');
const $textarea = document.querySelector('textarea');
const $main = document.querySelector('main');
function sendMessage() {
socket.emit('chat', {name, text: $textarea.value});
const div = document.createElement('div');
div.className = 'me';
div.innerHTML = `<b>${name}:</b> <span>${$textarea.value}</span>`;
$main.appendChild(div);
$textarea.value = '';
$main.scrollTo(0,$main.scrollHeight);
}
$button.addEventListener('click', sendMessage);
$textarea.focus();
</script>
</head>
<body>
<header>
<h1>🔒 Private Chat </h1>
</header>
<main>
<div class='you'>Welcome!</div>
<div class='me'></div>
</main>
<footer>
<textarea placeholder='Type your message here...'></textarea>
<button>Send</button>
</footer>
</body>
</html>
```
***
<h2 id='listofresources'>List of Resources</h2>
### HTML References
1. HTML Elements:<br> <https://developer.mozilla.org/en-US/docs/Web/HTML/Element>
### CSS References
1. Flex Box:<br> <https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox>
2. Grid Layout:<br> <https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout>
### Javascript References
1. Socket IO<br> <https://socket.io/>
<style>
img {
box-shadow:0 10px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
display:block;
margin: 1rem auto 3rem;
}
pre > code {
font-size: 16px;
color:lightgreen;
}
</style>