코드스테이츠 FE 부트캠프를 시작하고 한달동안 HTML, CSS, JS 을 학습했습니다.
한달간 배운 지식들로 solo project 과제를 진행했습니다.
이번 과제는 제공된 Mock Data를 이용하여 요구사항들을 구현하는 과제입니다.
주어진 9시간 안에 HTML, CSS, JS 모두를 구현해야 했기에, CSS보다는 JS를 이용한 기능 개발에 초점을 맞추어 개발하였습니다.
Mock Data는 아래와 같은 객체들로 이루어진 배열입니다.
const agoraStatesDiscussions = [
{
id: "D_kwDOHOApLM4APjJi",
createdAt: "2022-05-16T01:02:17Z",
title: "koans 과제 진행 중 npm install 오류로 인해 정상 작동 되지 않습니다",
url: "https://github.com/codestates-seb/agora-states-fe/discussions/45",
author: "dubipy",
answer: {
id: "DC_kwDOHOApLM4AKg6M",
createdAt: "2022-05-16T02:09:52Z",
url: "https://github.com/codestates-seb/agora-states-fe/discussions/45#discussioncomment-2756236",
author: "Kingsenal",
bodyHTML:
'안녕하세요.',
avatarUrl: "https://avatars.githubusercontent.com/u/79903256?s=64&v=4",
},
bodyHTML:
'<p dir="auto">운영 체제: 예) macOS</p>\n<p dir="auto">현재 어떤 챕터/연습문제/과제를 진행 중이고, 어떤 문제에 부딪혔나요?<br>\nPair 과제 / JavaScript Koans</p>\n<p dir="auto">npm install 명령어 입력 시 env: node: No such file or directory 라고 뜹니다</p>,
avatarUrl:
"https://avatars.githubusercontent.com/u/97888923?s=64&u=12b18768cdeebcf358b70051283a3ef57be6a20f&v=4",
},
객체를 인자로 받아 HTML 구조를 만들어 리턴하는 함수와 화면에 렌더링 해주는 함수입니다.
렌더링 할 태그들과 클래스를 만들었고, 각 태그 안에 필요한 정보들을 할당해주었습니다.
const convertToDiscussion = (obj) => {
const li = document.createElement("li");
li.className = "discussion__container";
const avatarWrapper = document.createElement("div");
avatarWrapper.className = "discussion__avatar--wrapper";
const discussionContent = document.createElement("div");
discussionContent.className = "discussion__content";
const discussionAnswered = document.createElement("div");
discussionAnswered.className = "discussion__answered";
// image
const avatarImg = document.createElement("img");
avatarImg.className = "discussion__avatar--image";
avatarImg.src = obj.avatarUrl;
avatarImg.alt = "avatar of" + obj.author;
avatarWrapper.append(avatarImg);
// content-title
const contentTitle = document.createElement("h2");
contentTitle.className = "discussion__title";
discussionContent.append(contentTitle);
const contentLink = document.createElement("a");
contentLink.href = obj.url;
contentLink.textContent = obj.title;
contentTitle.append(contentLink);
// content-information
const contentInformation = document.createElement("div");
contentInformation.className = "discussion__information";
contentInformation.textContent = obj.author;
discussionContent.append(contentInformation);
// answer
const checkAnswered = document.createElement("p");
discussionAnswered.append(checkAnswered);
obj.answer
? (checkAnswered.textContent = "☑")
: (checkAnswered.textContent = "☒");
if (checkAnswered.textContent === "☒") {
checkAnswered.style.color = "red";
}
li.append(avatarWrapper, discussionContent, discussionAnswered);
return li;
};
const render = (element, startIdx, endIdx) => {
for (let i = startIdx; i < endIdx; i += 1) {
element.append(convertToDiscussion(agoraStatesDiscussions[i]));
}
return;
};
contentInformation.textContent = `${obj.author} / ${new Date(obj.createdAt).toLocaleString("ko-KR")}`;
obj.answer
? (checkAnswered.textContent = "☑")
: (checkAnswered.textContent = "☒");
if (checkAnswered.textContent === "☒") {
checkAnswered.style.color = "red";
}
form에서 입력받은 정보들로 새로운 객체를 만들었고, 기존 Data 를 삭제한 뒤 Data 배열의 맨 앞에 추가해주고 다시 렌더링 해주었습니다.
prepend() 메서드를 이용하는 방법이 더 간단하지만, 뒤에서 설명할 페이징 기능으로 인해 render 함수를 이용해 구현했습니다.
submitBtn.addEventListener("click", (e) => {
e.preventDefault();
const newDiscussion = {
id: Date.now(),
createdAt: new Date(),
title: inputTitle.value,
url: "https://github.com/codestates-seb/agora-states-fe/discussions/45",
author: inputName.value,
answer: null,
bodyHTML: inputStory.value,
avatarUrl:
"https://avatars.githubusercontent.com/u/95295766?s=64&u=85d493e0be0d2ca55965efd9f6c5b268c9dca168&v=4",
};
removeChildes(ul);
agoraStatesDiscussions.unshift(newDiscussion);
render(ul, startIdx, endIdx);
formReset();
});
const removeChildes = (el) => {
while (el.firstChild) {
el.firstChild.remove();
}
};
각각의 input 태그에 유효성 검사 함수를 추가하여 submit 버튼이 disabled 되도록 구현하였습니다.
const formValidate = () => {
if (inputName.value && inputTitle.value && inputStory.value) {
submitBtn.disabled = false;
} else {
submitBtn.disabled = true;
}
};
const inputArr = [inputName, inputTitle, inputStory];
inputArr.forEach((el) => {
el.addEventListener("keyup", () => {
formValidate();
});
});
UX 향상을 위해 form을 submit 한 뒤 input 창의 값들을 초기화 했고, toggle 메서드와 focus 메서드를 사용했습니다.
const formReset = () => {
inputName.value = "";
inputTitle.value = "";
inputStory.value = "";
formContainer.classList.add("hide");
submitBtn.disabled = true;
};
newDiscussionBtn.addEventListener("click", () => {
formContainer.classList.toggle("hide");
inputName.focus();
});
시작인덱스와 종료인덱스를 뜻하는 변수 startIdx, endIdx를 선언하여
NEXT버튼과 PREV버튼을 누를 때 마다 다른 화면을 렌더링 해주었습니다.
ex) 데이터 배열의 0 ~ 10번째, 10 ~ 20번째, 20 ~ 30번째 ...
let startIdx = 0;
let endIdx = 10;
nextPageBtn.addEventListener("click", () => {
if (endIdx > agoraStatesDiscussions.length) return;
startIdx += 10;
endIdx += 10;
removeChildes(ul);
render(ul, startIdx, endIdx);
});
prevPageBtn.addEventListener("click", () => {
if (startIdx <= 0) return;
startIdx -= 10;
endIdx -= 10;
removeChildes(ul);
render(ul, startIdx, endIdx);
});
첫번째 페이지에서 PREV 버튼을 눌렀을 때와
마지막 페이지에서 NEXT 버튼을 눌렀을 때에는
현재의 페이지를 유지하기 위해 다음과 같은 조건문을 추가했습니다.
if (endIdx > agoraStatesDiscussions.length) return;
if (startIdx <= 0) return;
Local Storage에 데이터를 저장할 때는 문자열로 변환해주고,
저장한 Local Storage를 불러올때는 다시 객체로 변환해주어야 하기 떄문에
JSON.stringify(), JSON.parse() 를 사용했습니다.
const DISCUSSIONS_KEY = "discussions";
const saveDiscussion = () => {
localStorage.setItem(DISCUSSIONS_KEY, JSON.stringify(agoraStatesDiscussions));
};
const savedDiscussions = localStorage.getItem(DISCUSSIONS_KEY);
const parsedDiscussions = JSON.parse(savedDiscussions);
saveDiscussion();
주어진 시간 내에 요구사항을 모두 구현했습니다.
페이징 기능 구현 부분에서 많이 막혀 구글링을 오랜 시간 했는데,
다른 분들이 구현한 방법은 생각보다 코드도 복잡하고 코드량도 굉장히 많아서,
구현하는 방식만 참고하여 저만의 방법으로 더욱 간결하게 페이징 기능을 구현했습니다.
하나의 자바스크립트 파일에 모든 기능을 구현했습니다.
이후 확장성과 유지보수를 위해 기능별로 자바스크립트 파일을 나누고, class를 이용해 OOP로 개발했으면 하는 아쉬움이 있습니다.
또한 기능구현에 집중하느라 초기 목업 디자인에 신경을 많이 쓰지 못했는데, 기능구현 도중 중간중간 CSS파일을 수정하느라 오히려 생산성이 저하된 것 같았습니다.
앞으로는 초기 목업 디자인을 설정하고, 이에 맞게 확실하게 목업 한 뒤 기능구현을 시작해야겠다는 생각이 들었습니다.
지금까지는 문서, 강의를 통해 알려주는 대로 개발 공부를 했지만,
처음으로 요구사항에 맞게 저만의 방법으로 개발해 볼 수 있는 값진 시간이었습니다.
이번 프로젝트를 진행하며 중간중간 막히고 어려운 부분도 있었지만,
그동안 열심히 공부했던 노력들이 헛되지 않았다는 긍정적인 생각도 많이 들었고, 자신감도 생기는 계기가 되었습니다.