상세 컨텐츠

본문 제목

방명록 만들기 2

내일배움캠프 학습/HTML

by 남민우_ 2024. 10. 31. 22:34

본문

지난 시간에 만들었던 방명록을 이어서 제작한다.

먼저 버그 사항부터 수정하고 시작한다

데이터 Read 순서 정렬

let docs = await getDocs(query(collection(db, "DB_guestLog"),
	orderBy("date", "asc"), orderBy("hour", "desc"), orderBy("minu", "desc")));

코드를 다음과 같이 수정하였다.

date를 asc 로만 받아올 때 같은 날짜 안에 시간이 다를 경우 의도하지 않은 순서로 정렬되는 현상을 고치기 위해 hour, minu 필드를 추가, 각각 desc 로 받도록 하였다.

 

desc 로 진행한 이유는 날짜와 동일하게 asc 로 정렬할 경우 순서가 반대로 적용되어 내림차순인 desc 로 정렬한 후 받아왔다.

hour 에는 시간, minu 에는 분 을 저장하여 같은 날짜 같은 시간 안에서도 순서가 정렬될 수 있게 하였다.

더 정확하게 하자면 seco 필드를 추가해 초 단위까지 저장하는 것이 맞지만, 그렇게까지 디테일한 방명록을 만들고자 하는 것은 아니기 때문에 분 단위에서 멈췄다.

 

이를 FireStore DataBase 에서 Index 를 활용해 각 필드별로 순서를 정확하게 지정해주었다.

 

 

또한 시간을 저장하는 방식도 조금 변경하였다.

$("#id_Write-Button").click(async function () {
	let uniqueId = uuid.v4();
	let name = $('#guest_name').val();
	let comment = $('#guest_comment').val();
	if (comment == "" || name == "") {
		let blank = '내용';
		if (name == "") blank = '이름';
		alert(`${blank}을 입력해주세요.`);
		return;
	}

	let date = new Date();
	let date_Hour = date.getHours();
	let date_minu = date.getMinutes();

	let formatDate = `${date.getMonth() + 1}/${date.getDate()},`;

	let doc = {
		'UniqueId': uniqueId,
		'name': name,
		'comment': comment,
		'date': formatDate,
		'hour': date_Hour,
		'minu': date_minu
	};
	await addDoc(collection(db, "DB_guestLog"), doc);
	alert('댓글이 입력되었습니다!');
	window.location.reload();
})

uniqueId 에 대해서는 이후에 설명한다.

 

name, comment 는 그대로 저장하지만 기존의 date 는 date_Hour, date_minu 로 나누어 각각 시간과 분을 따로 저장할 수 있게 하였다. 위에서 시간과 분을 구분하여 순서를 더 정확하게 정렬하기 위함의 일환이다.

 

이후 데이터 Read 시에는 이 date_Hour 와 date_minu 를 그대로 출력하는 것이 아니라 함수를 사용해 데이터를 가공, Am 과 Pm 을 붙여 반환하여 출력할 수 있도록 하였다.

//시간 데이터 가공 함수
function formatTime(hour, minu) {
	let jyp = 'Am';
	if (hour >= 12) {
		jyp = 'Pm';
		hour = hour > 12 ? hour - 12 : hour;
	}
	if (hour === 0) hour = 12;

    return `${hour}:${String(minu).padStart(2, '0')}${jyp}`;
}

 

//데이터 출력 코드 중 일부
let Time = formatTime(row['hour'], row['minu']);
let log_guestHtml = `
	<div class = "Log-Block" id = "${uniqueId}">
		<div class="Log-id">
			<div>${name}</div>
			<div>${comment}</div>
		</div>
		<button id = "reLog-btn" type="button" class="btn btn-link">답글</button>
		<div class="Log-date">
 			<div>${date} ${Time}</div>
 		</div>
 	</div>
`;

$('#LogContainer').append(log_guestHtml);

 

이렇게 댓글을 정확히 저장 및 Read 시 의도한 순서에 따라 정렬할 수 있도록 진행하였다.

 

위에 댓글에 uniqueId 를 선언하는 코드가 있었는데, 그 이유는 댓글에 대댓글(이하 답글) 기능을 만들기 위해서이다.

답글 기능 구현

완성된 모습을 미리 보여주면 다음과 같다.

위의 'dd - dd' 와 '박찬우 - 이게 삼성조의 방명록...??' 부분이 댓글, 그 하위로 달린 'dd - dd', '남민우 - 형이야~', '박찬우 - 너무 좋군' 이 답글이 된다.

또한 댓글 우측에 답글 버튼이 보이는데 이를 누르면

클릭한 댓글의 하위에 Input Field 가 생성되고, 저 빈칸에 이름과 답글을 남길 수 있다.

이 답글 버튼은 토글 기능을 추가하여 다시 누를 경우 Input Field 가 삭제되는 기능까지 구현하였다.

 

정리하자면 답글의 주요 기능은 다음과 같다.

1. Input Field Block 생성 및 토글 기능

2. 답글 입력 및 저장

3. 답글 Read 시 댓글의 하위에 생성

4. 댓글과 마찬가지로 순서 정렬

 

차례대로 보도록 하자.

1.  Input Field Block 생성 및 토글 기능

사실 내용 자체는 간단한데, 먼저 Input Field 를 floatingInput 으로 생성한다. 구조 자체는 가장 먼저 만들었던 '방명록을 작성해주세요' 칸과 동일하다.

<div class = "ReLog-Writer" id = "ReLogWriter" style = "display:none">
	<div class="ReLog-Name-Float">
		<input type="email" class="form-control" id="ReLog-Name" placeholder="이름">
		<label for="floatingInput"></label>
	</div>
	<div class="Relog-Comment-Float">
		<input type="email" class="form-control" id="ReLog-Comment" placeholder="답변을 남겨주세요">
		<label for="floatingInput"></label>
	</div>
	<button id="id_Write-Button" type="button" class="btn btn-outline-primary">등록</button>
</div>

구조는 위와 같이 잡고, 여러 class 나 id 에 답글 을 뜻하는 Re 를 달아주었다.

또한 처음부터 생성되는 것이 아니라 버튼을 눌러야 생성되게 만들 것이기 때문에 style = "display:none" 을 추가적으로 넣어주었다.

 

주요한 부분은 답글 버튼을 눌렀을 때 이 Input Field 가 생성되는 것이다.

let relog_writer = `
	<div class = "ReLog-Writer" id = "ReLogWriter">
		<div class="ReLog-Name-Float">
			<input type="email" class="form-control" id="ReLog-Name" placeholder="이름">
			<label for="floatingInput"></label>
		</div>
		<div class="Relog-Comment-Float">
			<input type="email" class="form-control" id="ReLog-Comment" placeholder="답변을 남겨주세요">
			<label for="floatingInput"></label>
		</div>
		<button id="id_Write-Button" type="button" class="btn btn-outline-primary">등록</button>
	</div>
`;

$('#LogContainer').on('click', '#reLog-btn', function () {
	const currentLogBlock = $(this).closest('.Log-Block');

	const existingReLogWriter = currentLogBlock.next('.ReLog-Writer');

	$('.ReLog-Writer').remove();

	if (existingReLogWriter.length === 0) {
		currentLogBlock.after(relog_writer);
	}
});

변수 relog_writer 를 선언, 미리 만들어두었던 구조를 대입시켜준다.

이 relog_writer를 불러오는 것으로 답글 입력칸을 만들 것이다.

 

밑의 함수에서는 본격적으로 생성과 토글 기능을 만든다.

다만 주의할 점으로 이 relog_writer 는 미리 만들어져있는 body에 연결하는 것이 아니라 생성된 댓글에 달려있는 버튼, 즉 동적으로 만들어지는 버튼에 연결해야 한다.

해서 미리 만들어진 body를 찾고 그 안에서 우리가 원하는 하위 객체를 찾아야 한다.

$('#LogContainer').on('click', '#reLog-btn', function () {

이 코드로 이루어진다.

미리 만들어진 body, LogContainer(댓글 전체를 포함하는 body) 를 먼저 찾고,

그 안에서 reLog-btn 이라는 id를 찾는다. 이 reLog-btn 은 댓글의 답글 버튼 id이다.

이 reLog-btn 을 찾으면 그 버튼에 'click' 기능을 연결해주는 과정이다.

 

const currentLogBlock = $(this).closest('.Log-Block');

이 코드에서는 curreentLogBlock 에 댓글 body 를 찾아서 대입한다.

this 에는 reLog-btn 이 들어가고 이와 가장 가까운 Log-Block을 closest() 함수를 통해 탐색 후 그 값을 currentLogBlock 에 넣어준다.

 

const existingReLogWriter = currentLogBlock.next('.ReLog-Writer');

$('.ReLog-Writer').remove();

이 두 코드가 토글 기능을 가능하게 한다.

currentLogBlock 의 다음 ReLog-Writer를 찾아서 existingReLogWriter 에 대입한다.

만약 답글 입력칸(ReLog-Writer) 가 이미 생성되어 있다면 이 existingReLogWriter 에 값이 들어갈 것이고, 이후 remove() 된다.

왜 바로 remove() 를 하는지 의문이 들 수 있는데, 상황을 생각해보면 된다.

 

1. ReLog-Writer 가 이미 만들어져있는 경우

만들어져 있는 경우에 답글 버튼을 눌렀으면 또 만들 이유가 없다.

또한 토글 기능을 구현할 것이기 때문에 자연스럽게 사라지도록 remove() 하는 것이다.

2. ReLog-Writer 가 없는 경우

만들어져 있지 않은 경우에는 existingReLogWriter 에 null 값이 들어간다. 존재하지 않는 것을 찾는 코드니 당연하다.

따라서 remove() 함수가 동작하지 않고 넘어간다.

 

if (existingReLogWriter.length === 0) {
	currentLogBlock.after(relog_writer);
}

 이후 이 조건문을 통해 ReLog-Writer 를 생성하는데 위치는 currentLogBlock 의 아래, 즉 after 로 생성한다.

기존에는 append() 를 통해 생성해 바로 다음 위치에 생성하지만 이 append() 로 생성할 경우 댓글 오른쪽에 생성이 된다.

나는 그 아래, 다음 위치를 원하는 것이기 때문에 after 로 생성해주도록 했다.

 

2. 답글 입력 및 저장

입력 과정은 기존 댓글 입력과 유사하다.

$('#LogContainer').on('click', '.ReLog-Writer #id_Write-Button', async function () {
	let upponId = $(this).closest('.ReLog-Writer').prev('.Log-Block').attr('id');
	let re_name = $('#ReLog-Name').val();
	let re_comment = $('#ReLog-Comment').val();
	if (re_name == "" || re_comment == "") {
		let blank = '내용';
		if (re_name == "") blank = '이름';
		alert(`${blank}을 입력해주세요.`);
		return;
	}

let re_date = new Date();
let re_date_Hour = re_date.getHours();
let re_date_minu = re_date.getMinutes();

let re_formatDate = `${re_date.getMonth() + 1}/${re_date.getDate()}`;

let re_doc =
	{
		'upponId': upponId,
		're_name': re_name,
		're_comment': re_comment,
		're_date': re_formatDate,
		're_hour': re_date_Hour,
		're_minu': re_date_minu
	};

	console.log(re_doc);
	await addDoc(collection(db, "DB_reLog"), re_doc);
	alert('답글이 입력되었습니다!');
	window.location.reload();
});

코드 자체는 조금 길다. 하나씩 살펴보자.

 

버튼에 click 기능을 연결하는 과정은 위에서 설명한 것과 동일하기에 넘어간다.

다만 여기서는 async function 을 사용하였는데, 답글에서는 저장해야 할 필드가 더 많기 때문에 효율적으로 처리하고자 추가하였다.

비동기 함수로 작동한다는 점은 알고 있지만 일반 function과 정확히 어떤 차이, 어떤 방식으로 작동하는지는 추가적으로 공부가 필요한 사항이다.

 

이어서 설명하자면 name comment 등 여러 필드를 받아오는 것과 값을 DB에 저장 후 reload() 하는 과정까지 댓글과 전부 동일하다.

다만 다른 점으로는

let upponId = $(this).closest('.ReLog-Writer').prev('.Log-Block').attr('id');

이 코드가 추가되었다는 점이 될 텐데, 먼저 댓글의 코드를 살펴보고 설명하는 것이 순서에 맞을 것이다.

 

<script src="https://cdnjs.cloudflare.com/ajax/libs/uuid/8.3.2/uuid.min.js"></script>

let uniqueId = uuid.v4();

위의 코드에서 uuid 라이브러리 연결 및 uniqueId 에는 이 uuid.v4() 를 통해 고유 식별자를 생성하도록 하였다.

답글이 정확한 위치에 생성되기 위해서는 어느 댓글에서 작성되었는지를 판단해야 하는데, 초기에는 name 으로 작성하려다 name 이 같은 경우에는 어느 위치인지 판단을 어떻게 하지? 라는 의문이 들어 수정한 방향이다.

이름 외에도 날짜, 시간 등 기존의 다른 필드들은 모두 이에 해당한다.

 

따라서 uuid.v4() 를 통해 고유한 id 를 생성 후 댓글 DB에 이를 같이 저장한다.

이 uuid.v4() 는 

사진 예시처럼 같은 id가 생성될 확률이 지극히 낮은, 없다시피한 랜덤값을 생성한다.

따라서 이 값을 고유 id로 사용하기로 했다.

 

$('#LogContainer').on('click', '.ReLog-Writer #id_Write-Button', async function () {
	let upponId = $(this).closest('.ReLog-Writer').prev('.Log-Block').attr('id');

이제 답글에서 이 id값을 저장해야 한다.

구조를 먼저 생각해보면 하위로 달려야 하는 댓글도 동적으로 생성되는 객체다.

따라서 윗줄의 코드를 통해 동적 생성 객체의 id (id_Write-Button) 을 받아와 this에 먼저 할당, 이후 제일 가까운 ReLog-Writer를 찾고 그 Writer 의 이전 Log-Block 을 찾는다.

 

먼저 ReLog-Writer 를 찾는건 댓글 입력도 결국 이 ReLog-Writer 에서 이뤄지기 때문이다.

따라서 입력의 주체가 되는 Writer 를 먼저 찾는데, 이 또한 Log-Block 의 after 로 생성된다.

위에서 설명한 코드를 다시 보고 오면 이해가 될 것이다.

우리는 이 Log-Block 의 밑에 답글을 생성해야 하기 때문에 prev() 를 통해 답글 객체를 받아온다.

 

여기까지 실행한 이후, attr('id') 를 통해 이 Log-Block 의 id, uuid.v4() 를 통해 생성된 고유 id 를 받아온다.

테스트를 위해 댓글과 답글을 입력하고, DataBase 에 가서 id 값을 확인하면 동일한 값인 것을 볼 수 있다.

 

여기까지 완료되었다면 나머지 동작들은 간단하다.

3 + 4. 답글 Read 시 댓글의 하위에 생성 + 댓글과 마찬가지로 순서 정렬

3번과 4번은 사실 하나의 동작이라고 봐도 무방하기에 한번에 설명한다.

구조 또한 댓글을 생성할 때와 유사하다.

let re_docs = await getDocs(query(collection(db, "DB_reLog"),
	orderBy("re_date", "asc"), orderBy("re_hour", "asc"), orderBy("re_minu", "asc")));
re_docs.forEach((re_doc) => {
	let re_row = re_doc.data();

	let re_uniqueId = re_row['upponId'];
	let re_name = re_row['re_name'];
	let re_comment = re_row['re_comment'];
	let re_date = re_row['re_date'];
            
	let re_Time = formatTime(re_row['re_hour'], re_row['re_minu']);
	let relog_guestHtml = `
		<div class="ReLog-Block" id="ReLogBlock">
			<div class="ReLog-id">
				<div>└</div>
				<div class = "ReLog-other">
				<div>${re_name}</div>
				<div>${re_comment}</div>
			</div>
		</div>
		<div class="ReLog-date">
			<div>${re_date}, ${re_Time}</div>
		</div>
	</div>
`;
$(`#${re_uniqueId}`).after(relog_guestHtml);
});

가장 밑의 코드부터 설명하자면 변수 relog_guestHtml 에 만들어진 값을 re_uniqueId (DateBase에서의 upponId) 를 id로 가진 객체의 after 로 생성한다.

답글 Input Field 를 만들 때와 동일하다.

 

name, comment, date, Time 값 또한 댓글과 동일한 과정으로 이루어진다.

 

다만 정렬 순서를 이번에는 re_date 뿐만 아니라 re_hour, re_minu 모두 asc 로 오름차순 정렬하였는데, 여기서 시행착오가 있었다.

댓글 때와 동일하게 re_hour, re_minu 모두 처음에는 desc 로 정렬했다가, 테스트를 해보니 답글은 반대로 출력되는 것이었다.

 

다시 생각해보니 이유가 있는 과정이었는데, 이 답글 원리를 생각해보면 생성 자체가 after 로 생성되기 때문이다.

desc 로 정렬할 경우 댓글은 최신이 위에 올라오는 모습, 그러니까 오래된 값일수록 늦게 출력되는 것이다.

하지만 답글의 경우 오래된 값이 늦게 출력될 경우 after 로 생성하다보니

다음과 같이 순서가 반대로 뒤집어지는 현상이 나타난다.

 

따라서 그 반대 정렬인 asc 로 지정해주었다.

 

마무리

이렇게 방명록의 기능을 모두 구현하였다.

추가적으로 구현할 수 있는 것들이라면 수정, 삭제 등의 기능이 있겠지만 이는 더 현실성을 살리자면 이용자의 아이디와 같은 고유 id 값을 받고, 이를 검증 후 수정하는 등 더 복잡한 작업이기에 현재의 기량과 일정 상 무리라고 판단하여 진행하지 못했다.

이후에 기회가 있다면 도전해볼만 할 것 같다.

 

완성된 방명록의 모습은 다음과 같다.

'내일배움캠프 학습 > HTML' 카테고리의 다른 글

방명록 만들기  (0) 2024.10.30
#5. 웹페이지 배포  (1) 2024.10.29
#4. FireBase 활용  (1) 2024.10.29
#3. JS 실습  (2) 2024.10.28
#2. JavaScript 활용  (3) 2024.10.28

관련글 더보기