실시간 GIF 채팅방 만들기(2)
채팅 웹 메인 화면과 채팅방 등록 화면을 만들어보겠습니다. 채팅뿐만 아니라 채팅방도 실시간으로 추가되거나 제거됩니다.
화면의 레이아웃을 담당하는 layout.html 파일을 작성하고 views/error.html을 수정합니다.
views/layout.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>{{title}}</title>
<link rel="stylesheet" href="/main.css" />
</head>
<body>
{% block content %}
{% endblock %}
{% blockscript %}
{% endblock %}
</body>
</html>
views/error.html
{% extends 'layout.html' %}
{% block content %}
<h1>{{message}}</h1>
<h2>{{error.status}}</h2>
<pre>{{error.stack}}</pre>
{% endblock %}
main.css를 추가하여 간단히 디자인합니다.
public/main.css
* {
box-sizing: border-box;
}
.mine {
text-align: right;
}
.system {
text-align: center;
}
.mine img,
.other img {
max-width: 300px;
display: inline-block;
border: 1px solid silver;
border-radius: 5px;
padding: 2px 5px;
}
.mine div:first-child,
.other div:first-child {
font-size: 12px;
}
.mine div:last-child,
.other div:last-child {
display: inline-block;
border: 1px solid silver;
border-radius: 5px;
padding: 2px 5px;
max-width: 300px;
}
#exit-btn {
position: absolute;
top: 20px;
right: 20px;
}
#chat-list {
height: 500px;
overflow: auto;
padding: 5px;
}
#chat-form {
text-align: right;
}
label[for="gif"],
#chat,
#chat-form [type="submit"] {
display: inline-block;
height: 30px;
vertical-align: top;
}
label[for="gif"] {
cursor: pointer;
padding: 5px;
}
#gif {
display: none;
}
table,
table th,
table td {
text-align: center;
border: 1px solid silver;
border-collapse: collapse;
}
이제 메인 화면을 담당하는 main.html 파일을 작성합니다.
views/main.html
{% extends 'layout.html' %} {% block content %}
<h1>GIF 채팅방</h1>
<fieldset>
<legend>채팅방 목록</legend>
<table>
<thead>
<tr>
<th>방 제목</th>
<th>종류</th>
<th>허용 인원</th>
<th>방장</th>
</tr>
</thead>
<tbody>
{% for room in rooms %}
<tr data-id="{{room._id}}">
<td>{{room.title}}</td>
<td>{{'비밀방' if room.password else '공개방'}}</td>
<td>{{room.max}}</td>
<td style="color: {{room.owner}}">{{room.owner}}</td>
<td>
<button
data-password="{{'true' if room.password else 'false'}}"
data-id="{{room._id}}"
class="join-btn"
>
입장
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="error-message">{{error}}</div>
<a href="/room">채팅방 생성</a>
</fieldset>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io.connect("http://localhost:8005/room", {
// 네임스페이스
path: "/socket.io",
});
socket.on("newRoom", function (data) {
// 새 방 이벤트 시 새 방 생성
const tr = document.createElement("tr");
let td = document.createElement("td");
td.textContent = data.title;
tr.appendChild(td);
td = document.createElement("td");
td.textContent = data.password ? "비밀방" : "공개방";
tr.appendChild(td);
td = document.createElement("td");
td.textContent = data.max;
tr.appendChild(td);
td = document.createElement("td");
td.style.color = data.owner;
td.textContent = data.owner;
tr.appendChild(td);
td = document.createElement("td");
const button = document.createElement("button");
button.textContent = "입장";
button.dataset.password = data.password ? "true" : "false";
button.dataset.id = data._id;
button.addEventListener("click", addBtnEvent);
td.appendChild(button);
tr.appendChild(td);
tr.dataset.id = data._id;
document.querySelector("table tbody").appendChild(tr); // 화면에 추가
});
socket.on("removeRoom", function (data) {
// 방 제거 이벤트 시 id가 일치하는 방 제거
document.querySelectorAll("tbody tr").forEach(function (tr) {
if (tr.dataset.id === data) {
tr.parentNode.removeChild(tr);
}
});
});
function addBtnEvent(e) {
// 방 입장 클릭 시
if (e.target.dataset.password === "true") {
const password = prompt("비밀번호를 입력하세요");
location.href = "/room/" + e.target.dataset.id + "?password=" + password;
} else {
location.href = "/room/" + e.target.dataset.id;
}
}
document.querySelectorAll(".join-btn").forEach(function (btn) {
btn.addEventListener("click", addBtnEvent);
});
</script>
{% endblock %} {% block script %}
<script>
window.onload = () => {
if (new URL(location.href).searchParams.get("error")) {
alert(new URL(location.href).searchParams.get("error"));
}
};
</script>
{% endblock %}
io.connect 메서드의 주소가 달라졌다는 점에 주목해주세요. 주소 뒤에 /room이 붙었습니다. 이것을 네임스페이스라고 부르며, 서버에서 /room 네임스페이스를 통해 보낸 데이터만 받을 수 있습니다. 네임스페이스를 여러 개 구분해 주고받을 데이터를 분류할 수 있습니다.
socket에는 미리 newRoom과 removeRoom 이벤트를 달아두었습니다. 서버에서 웹 소켓으로 해당 이벤트를 발생시키면 이벤트 리스너의 콜백 함수가 실행됩니다. 콜백 함수의 내용이 길지만 특별한 것은 없습니다. 각각 테이블에 새로운 방 목록을 추가하거나 제거하는 코드입니다. 입장 버튼을 누르면, 비밀방일 경우 비밀번호를 받고 공개방일 경우 바로 입장시킵니다.
'프로그래밍 언어 > NODE JS' 카테고리의 다른 글
| 실시간 GIF 채팅방 만들기(4) (0) | 2026.01.29 |
|---|---|
| 실시간 GIF 채팅방 만들기(3) (0) | 2026.01.26 |
| 실시간 GIF 채팅방 만들기(1) (0) | 2026.01.20 |
| Socket.IO 사용하기 (0) | 2026.01.17 |
| ws 모듈로 웹 소켓 사용하기 (0) | 2026.01.14 |