[Toy Projects] 나만의 아고라스테이츠 만들기

Codestates - 나만의 아고라스테이츠 만들기

 

23년 3월 9~10일, 코드스테이츠에서의 한 달이 지나 Section 1의 마지막으로 솔로 프로젝트 '나만의 아고라스테이츠 만들기'가 이틀간 진행되었다.

 

 

동작설명

 

  • 질문 리스트 10개씩 페이지네이션
  • 질문 등록하면 질문 리스트에 디스커션 추가(현지 시간 반영)
  • 제목 검색 기능(Coz' 요구사항 외 개인적으로 추가한 기능)

 

## Bare Minimum Requirement Self Checklist

- [⭕️ ] 디스커션 나열 기능

    - [⭕️ ] `script.js`를 수정하여 `agoraStatesDiscussions` 배열의 데이터를 나열할 수 있게 구현합니다.

- [⭕️ ] CSS

    - [⭕️ ] 아고라 스테이츠 질문 리스트가 중앙으로 와야 합니다.

    - [⭕️ ] `style.css`를 수정하여 멋지고 아름답게 나만의 아고라 스테이츠를 꾸밉니다.

    - [𝗫 ] [colorhunt](https://colorhunt.co/palettes/popular), [dribbble](https://dribbble.com/)에서 적절한 색 조합, 디자인을 참고합니다.

- [⭕️ ] 디스커션 추가 기능

    - [⭕️ ] `script.js`를 수정하여 디스커션 추가 기능을 구현합니다.

    - [⭕️ ] `section.form__container` 요소에 새로운 아고라 스테이츠 질문을 추가할 수 있는 입력 폼을 제작합니다. 형식은 자유입니다.

    - [⭕️ ] 아이디, 본문을 입력하고 버튼을 누르면 실제 화면에 디스커션이 추가되어야 합니다.

    - [⭕️ ] `agoraStatesDiscussions` 배열에 추가한 데이터가 실제 쌓여야 합니다.

- [⭕️ ] Github Page 배포

  - [⭕️ ] Github Page 배포 기능을 이용하여 누구나 볼 수 있게 배포합니다.

- [⭕️ ] [코드스테이츠 fe-sprint-my-agora-states 리포지토리](https://github.com/codestates-seb/fe-sprint-my-agora-states)로 Pull Request

  - [⭕️ ] 나만의 아고라 스테이츠를 코드스테이츠 깃허브에 Pull request합니다.

  - [⭕️ ] 주어진 Pull request 형식에 따라주세요.

 

## Advanced Challenge Self Checklist

- [⭕️ ] 현지 시간 적용

    - [⭕️ ] 샘플 시간을 잘 변형하여, 현지 시간에 맞게 표현합니다. (ex. 오전 10:02:17)

- [⭕️ ] 페이지네이션 기능

    - [⭕️ ] 페이지네이션에 대해서 스스로 학습합니다.

    - [⭕️ ] 한 페이지에 10개씩 디스커션이 보여야 합니다.

    - [⭕️ ] 다음 페이지로 넘어갈 수 있어야 합니다.

    - [⭕️ ] 이전 페이지로 돌아올 수 있어야 합니다.

    - [⭕️ ] 다음 페이지가 없거나, 이전 페이지가 없는 경우 페이지를 유지해야 합니다.

- [𝗫 ] 디스커션 유지 기능

    - [𝗫 ] LocalStorage 대해서 스스로 학습하고, 새롭게 추가하는 Discussion 페이지를 새로고침해도 유지되도록 제작합니다.

 

 

사용스택

 

html, css, javascript

 

 

결과물

 

 

페이지네이션 동작화면

 

 

검색창 동작화면

 

 

 

 

고민했던 부분과 해결방법

 

1. 페이지네이션

페이지네이션을 위해 많은 코드와 설명을 읽었고 이해하려 노력했으나... 내 머리가 돌인건지 페이지네이션이 어려운건지 모를 정도였다. 일단 난이도를 조금 낮추기 위해 네비게이션 부분을 숫자가 아닌 이전페이지, 다음페이지 두 가지 버튼으로만 동작하게끔 하기로 하고 직접 코드 작성해보며 하나하나 부딪히다보니 어느정도 원리가 이해가 되었다. 

//페이지네이션
const numOfContent = agoraStatesDiscussions.length + 1; //글 개수(+1은 배열이 아닌 html상에도 디스커션이 하나 있어서 더해 준 것.)
const showContent = 10; //한 페이지당 글 개수
const maxPage = Math.ceil(numOfContent / showContent); //필요한 페이지 수
let page = 1; //현재페이지

function startAndLastPage(page){
  //1p(0~9), 2p(10~19), 3p(20~29)
  //첫 번째 디스커션
  let startContent = (page-1)*showContent; //(3페이지-1)*10 -> 20
  //마지막 디스커션
  let lastContent = startContent+showContent;
  //만들어진 페이지 범위 넘어가면 고정
  if(startContent<0){
    startContent = 0;
    lastContent = showContent;
  } else if (lastContent>numOfContent-1){ 
    lastContent = numOfContent-1;
    startContent = Math.floor(lastContent/10)*10;
  }
  return {startContent : startContent, lastContent : lastContent}
}

전체 데이터 개수, 한 페이지당 보여줄 데이터 개수, 필요한 페이지 수 이렇게 상수 3개와 현재 페이지를 알려줄 변수 하나가 우선 필요하다.

함수 안에서 위 상수와 변수를 이용해 각 페이지 내에서 보여줄 첫 번째 디스커션과 마지막 디스커션을 계산하여 리턴해준다. 두 가지 수를 리턴하기 위해서는 객체에 담아야 한다. 페이지가 끝났는데 계속 버튼을 누를 경우를 위해 페이지 양 끝 범위를 넘어가면 첫 번째 디스커션과 마지막 디스커션 수가 바뀌지 않게 고정해줘야 했는데, 시작 부분보다 끝나는 부분의 고정에 애를 좀 먹었다. 

 

리턴한 두 수를 이용한 페이지의 이동, 렌더링은 버튼에 onclick 이벤트를 줘서 아래와 같이 작성했다.

 

//이전페이지로
function beforePageEvent(){
  console.log('이전페이지 클릭');
  if(page < 2){
    page = 1;
  }else{
    page = page-1; //이전페이지로
  }
  let value = startAndLastPage(page);
  // console.log(value);
  let startContent = value.startContent;
  let lastContent = value.lastContent;
  console.log(startContent, lastContent);

  //앞에 render된 내용 삭제
  while (ul.firstChild) { 
    ul.removeChild(ul.firstChild);
  }
  
  render(ul, startContent, lastContent, agoraStatesDiscussions)
}
//다음페이지로
function nextPageEvent(){
  console.log('다음페이지 클릭');
  if(page > maxPage){
    page = maxPage
  }else{
    page = page+1; //다음페이지로
  }
  let value = startAndLastPage(page);
  // console.log(value);
  let startContent = value.startContent;
  let lastContent = value.lastContent;
  console.log(startContent, lastContent);

  //앞에 render된 내용 삭제
  while (ul.firstChild) { 
    ul.removeChild(ul.firstChild);
  }

  render(ul, startContent, lastContent, agoraStatesDiscussions)
}

지금 보니 beforePageEvent()와 nextPageEvent()는 겹치는 부분이 많아 하나의 함수로 만들고 페이지 이동 부분은 if문 등을 줘서 id값으로 구분해줘도 좋을 것 같다. 

 

 

2. 검색 기능

검색 기능은 사실 페이지네이션보다 먼저 만들었다가 수정을 많이 거치게 된 기능이다. 시간 안에 더 전체 퀄리티를 더 높히지 못 한 이유가 시키지도 않은 이 검색기능에 너무 많은 노력을 쏟아서이지 않나... 하지만 이번 프로젝트에서 가장 만족하는 부분.

페이지네이션을 하기 전에는 아래 코드와 같이 배열에서 검색된 요소를 제외한 나머지 요소의 css를 display: none으로 가려버리는 방법을 썼었다. 

 

//검색기능 (페이지네이션 전)
function search(){
  let code = document.querySelector('#search').value;
  // console.log(code);
  let discussionContainer = document.querySelectorAll('.discussion__container');
  let discussionTitle = document.getElementsByClassName('discussion__title');

  //입력받은 문구를 제목에 포함한 배열 요소 찾기
  for (let i=0; i<agoraStatesDiscussions.length; i++){
    //문구 제목에 포함한 요소는 보이게, 아닌 요소는 안 보이게
    if(agoraStatesDiscussions[i].title.includes(code)){
      discussionContainer[i+1].style.display = 'flex'
      //(i+1은 배열이 아닌 html상에도 디스커션이 하나 있어서 그걸 빼 준 것.)
    } else {
      discussionContainer[i+1].style.display = 'none';
    }
  }
  
  //html상의 디스커션도 검색에 포함(페이지네이션 후 수정본에선 포함하지 않음)
  if(discussionTitle[0].firstChild.textContent.includes(code)){
    discussionContainer[0].style.display = 'flex';
  } else {
    discussionContainer[0].style.display = 'none';
  }
}

 

하지만 페이지네이션을 하고 나니 페이지네이션에서 요구되는 렌더링과 검색에서 요구되는 렌더링이 충돌을 일으켜 검색 기능이 원활하게 작동하지 않았다. 결국 검색 기능과 렌더링 함수까지 전면 수정하여 렌더링 함수의 경우 데이터가 든 배열만 받던 것을 파라미터를 추가해 다양한 배열을 받을 수 있도록 고치고, 검색 함수는 검색된 요소를 새로운 배열에 다시 담고, 그 새로운 배열을 렌더링하는 방식으로 바꾸었다. 

 

//검색기능
function search(){
  let code = document.querySelector('#search').value;
  // console.log(code);
  let searchArr=[];
  let beforePage = document.getElementById("beforePage");
  let nextPage = document.getElementById('nextPage');
  let discussionContainer = document.querySelector('.discussion__container');
  
  //입력받은 문구를 제목에 포함한 배열 요소 찾아 새로운 배열에 추가
  for (let i=0; i<agoraStatesDiscussions.length; i++){
    if(agoraStatesDiscussions[i].title.includes(code)){
      searchArr.push(agoraStatesDiscussions[i]);
    }
  }
  if(code === ''){
    //앞에 render된 내용 삭제
    //(ul은 디스커션(질문) 리스트)
    while (ul.firstChild) { 
      ul.removeChild(ul.firstChild);
    }
    render(ul, 0, showContent-1, agoraStatesDiscussions); //렌더링 함수 따로 있음
    //페이지 이동 버튼 보이게
    beforePage.style.display = 'flex';
    nextPage.style.display = 'flex';
  } 
  else {
    //앞에 render된 내용 삭제
    while (ul.firstChild) { 
      ul.removeChild(ul.firstChild);
    }
    render(ul, 0, searchArr.length, searchArr);
    //페이지 이동 버튼 안보이게
    beforePage.style.display = 'none';
    nextPage.style.display = 'none';
  }
}

 

글로 적으니 간단하지만 하나의 아이디어를 생각해내고 적용하는데 꽤 시간이 걸리는데 정해진 짧은 시간 안에 전면 수정까지 거치다보니 나름 고생한, 하지만 뿌듯한 검색기능. 

 

 

3. 양쪽 배너 고정

남은 시간이 없었지만 세로로 긴 아고라스테이츠에서 양 사이드를 채워 넣어 조금이라도 css를 풍성하게 보이고 싶었고, 그 고민 끝에 생각한 방법이 사이드배너를 고정하는 형식이었다. 한 번도 해보지 않아서 이번 기회에 방법이 알고싶기도 했고, 다행히 아주 간단했다.

position을 sticky로 주고 top을 설정해주면 끝!

.form__container{
    position: sticky;
    top: 150px;
}

 

 

아쉬운 점

 

1. 페이지네이션 페이지 nav(네비게이션) 부분

페이지를 나누고 넘기는 원리를 이해하는 것 만으로도 벅차 nav부분을 잘 이해하지 못했다. 아쉬운대로 이전 페이지, 이후 페이지 두 개의 버튼으로만 페이지를 넘길 수 있도록 구현했는데 원하는 페이지 숫자를 눌러 원하는 페이지로 바로 이동할 수 있게 하는 부분은 추후 더 공부해야겠다.

 

2. 질문 등록시 1페이지가 아닌 현재 페이지의 상단에 등록되는 점

프로젝트가 끝나고 하루 이틀 후에 알게된 사실인데 질문을 등록하면 현재 페이지와 상관 없이 첫 번째 페이지 상단에 등록되어야 하는데 현재 페이지 상단에 등록된다. 이 부분은 코드를 다시 찬찬히 읽어봐야겠다.

 

3. 구현하지 못 한 디스커션 유지기능

advanced challenge 까지 거의 다 구현했지만 이 부분 만큼은 시간이 없어 손 댈 생각도 못 하고 끝나버렸다.

 

 

후기

이렇게까지 숨도 못 쉬고 잠도 자는둥 마는둥 몰입해서 코딩한건 처음이다. 타임어택이 있다보니 끝에는 손이 벌벌 떨렸다. 하지만 칙센트미하이의 책 "몰입의 즐거움" 처럼 몰입이란 즐거움이다. 어렵고 남보다 못하는 것 같고 잘못 선택한건 아닌가 싶을때마다 코딩의 즐거움을 잊지 않으려 노력하고 있다. 즐기자.

미니멈 요구사항 이상으로 구현해서 나름 만족하지만 아쉬운 부분도 많은건 사실이다. 한창 부트캠프 과정 중이라 개인적인 시간이 없다싶이 하지만 section2의 마지막 프로젝트에는 이번에 만든 '나만의 아고라스테이츠'의 서버를 구현하는 듯 하니 혹시나 짬을 낼 수 있다면 section2가 끝나기 전까지 아쉬운 부분을 조금이라도 수정해봐야겠다. 

 

 

github 주소 : https://github.com/AngryDoggaebi/FrontendMentor-Projects

 

 


 

Frontend Mentor 주소:

https://www.frontendmentor.io/home