이번 포스팅은 3월 9일, 10일에 진행했던 솔로 프로젝트 "나만의 아고라스테이츠 만들기" 진행 중 작성했던 코드들을 정리 할 것이다.
1. HTML
먼저 HTML 파일을 보면, form패그를 사용하여 폼에 제목, 이름, 질문내용을 작성할 수 있게 각 input을 주었다.
사실 포크해올 때 HTML 파일이 거의 대부분 만들어져 있어서 몇개만 바꾸고 추가해주기만해서 크게 건드리지 않았고,
많이 어려운 내용은 없었던 것 같다.
2. Javascript
진짜 어려운 점은 여기에서부터... 난관에 봉착.. 사실 자바스크립트 작성할 때 dom으로 HTML요소를 꺼내오긴했는데...
여기서 뭘 어떻게 해야할지 감조차 잡히지않았다 처음엔
하지만 유어클래스에서 튜토리얼로 간단하게 이런식으로 작성하면 된다~~ 이런 내용이 있어서 그걸 따라하다보니
대충 감이 잡혀서 하나하나 시작했던 것 같다.
⭐️ 코드를 뜯어서 한번 살펴보자.
1. 자바스크립트 작성하면서 HTML에 요소를 계속 생성해야해서 무슨 document.createElement를 계속 무한으로 써야될 것 같은 느낌이 들어서 바로 간단하게 함수를 주었다.
위처럼 createEl 함수를 따로 만들어 매개변수에 tagname, classname, textcontent를 주어 document.createElement()를 대신하였다.
2. 먼저, data.js에 들어있는 데이터 더미들을 받아와, 그 데이터들을 리스트화 해서 쭉 나열해야 하기때문에
그 작업을 먼저 진행했다.
먼저, convertToDiscussion 함수를 정의하였다. 함수에는 매개변수로 obj를 받고있다.
그다음 HTML요소를 추가할 것들을 변수로 선언을해준다
14, 16, 17, 18 번 줄은 각 변수에 createEl("태그", "클래스")를 통하여 태그와 클래스를 추가해주었다.
21번 줄도 위와같지만, 다른점은 setAttribute("속성이름", "속성값") 을 통하여 src속성에 obj.avatarUrl의 속성값을 주었다.
obj.avatarUrl은 data.js에서 obj하나의 avatarUrl의 값을 가져온다.
23번 줄은 append메서드를 사용하였다. 예를들어, A.append(B) 이면 A 요소의 마지막에 새로운 HTML 요소나 컨텐츠를 추가한다.
따라서 avatarImg를 avatarWrapper의 마지막에 추가하였다고 보면된다.
26번줄부터의 content 부분도 위의 avatar 부분과 비슷하지만, discussHref와 writeDay변수에 3개의 매개변수를 주었다.
createEl 함수에 3개의 변수가 들어가면 tagname, class, textcontent 이렇게 3개가 들어간다.
38번 줄부터도 위와 같이 진행하였다.
그리고 맨 마지막에 위의 3개(avatarWrapper, discussionContent, discussionAnswered)를 li의 마지막에 추가하였다.
그리고 li를 리턴하여 convertToDiscussion함수가 실행되면 li가 리턴되게 만들었다.
3. localStorage.getItem을 통해 로컬스토리지에 저장되어있는 키와 값들을 가져올 수 있다.
로컬스토리지에 있는 키이름이 "discussionStorage"인 값들을 반환하는 것이다.
하지만 조건문을 주어 null이 아닐때만 반환하게 해주었다.
JSON.parse(localStorage.getItem("discussionStorage"))는 JSON 문자열의 구문을 분석하여, 그 결과에서 data.js의 전체값들을 담고있는 agoraStatesDiscussions에 값이나 객체를 생성한다.
근데 로컬스토리지에서 받아오기만하고 어떻게 넣냐!!!!?? 그건 7번에서 살펴보도록하자!!
즉, 키가 discussionStorage 인 값이나 객체들을 로컬스토리지에서 받아와, agoraStatesDiscussions에 새로 생성하여준다.
4. agoraStatesDiscussions 배열의 모든 데이터를 화면에 렌더링하는 함수.
저는 한 페이지에 5개씩 보여주도록 하기 위해 렌더링된 첫화면에서 시작점을 0, 끝점을 5로 잡아서
render 함수를 정의했다.
element를 매개변수로 받고 64번 줄에서 렌더링 함수가 호출될때마다 ul.innerHTML=""; 을 통해 화면에 나온 리스트들을
한번 빈문자열로 만들어 비게 만들고, 다시 for문을 돌아 렌더링되게...?? (..맞나?) 만들었다.
for문을 보면 i를 renderStart로 초기화시키고, renderEnd의 전까지 돌게 만들어서
renderStart, renderEnd 각각 0, 5가 들어가있으므로, 5개씩 돌면서 append()메서드를 통해 element요소의 맨 마지막으로
agoraStatesDiscussions[i]를 매개변수로 하는 convertToDiscussion 함수의 리턴값이 들어가고, return을 통해 함수를 종료하였다.
즉, render함수가 호출되면 ul을 빈문자열로 초기화시키고, convertToDiscussion함수의 매개변수로 agoraStatesDiscussions[0~4]인 5개의 값들을 각각 13번째줄 obj에 들어가고, 그 결과값을 element요소의 맨마지막으로 추가해준다.
5. 질문을 제출할 때, 제출할 때의 시간과 날짜를 실시간으로 받아오게 하는 함수.
getDate라는 함수를 선언하여 함수가 호출될 때, Date 객체를 사용하여 날짜와 시간을 얻거나 생성했다.
77번 줄을 통해 today 변수에 현재 날짜와 시간을 넣어주는 식으로 하였다.
날짜는 78, 79, 85번 줄 year, month, day에 각각 today.getFullYear(), today.getMonth(), today.getDate() 메서드를 통하여 얻은
년, 월, 일을 각각 할당해준다.
🚨 여기서 주의할 점은 81, 83번 줄에 +1을 해주는 이유는 parseInt(month)를 하게 되면, 날짜를 문자열로 받아와서 숫자로 변환할 때 소수점이 생기는데, parseInt를 하게되면 소수점을 날리게 되므로 +1을 해야 원하던 값을 얻을 수 있는 것이다.
예를 들어, 8월이 나와야하는데 7.59328이 나왔다고 하고, parseInt(7.59328)을 넣게되면 7이 되므로 +1을 하여 8을 만들어야하기 때문이다.
시간은 89, 90, 91 번 줄을 통하여 위와같이 hour, minute, second에 각각 할당했음.
따라서 getDate함수가 호출되면 각 변수들의 값을 템플릿 리터럴을 통하여 반환해준다.
6. 이제 본격적으로 새로운 질문을 추가할때의 함수를 정의해본다. 하지만 사실상...체감상으로 제일 간단했...음
먼저 95, 96, 97 번줄은 HTML에서 id가 각각 name, title, story인 요소를 받아와 변수에 넣어주었다.
submitDiscussion 함수를 정의한 것을 보면 새로운 질문은 객체형태로 받아와야 하기때문에
100번 줄을 통해 discussionObject라는 변수를 선언하고 빈객체의 형태로 해주었다.
101번 줄은 위의 5번항목에서 설명한 getDate함수를 호출하여 리턴값을 date 변수안에 넣어줌.
그리고 선언했던 빈객체 discussionObject 안에 103~109 번줄까지를 통하여 하나하나 값을 집어넣어주었다.
103번줄을 예로들면 discussionObject객체 안에 키가 bodyHTML 이고, 값이 question.value인 프로퍼티를 넣어준 것이다.
그렇게 109번 줄까지 넣어주고, discussionObject 객체를 반환해준다.
정리해보면, id가 각각 name, title, story인 input을 author, title, question 변수에 각각 불러오고,
submitDiscussion함수를 호출하면, 103~109번를 통하여 만들어진 프로퍼티들을 빈 객체인 discussionObject에 넣어주고
그렇게 채워진 discussionObject객체를 리턴함.
7. 6번에서의 과정을 통해 input 3개에 값들을 입력하여 객체discussionObject를 채웠다면, 그걸 써먹어야겠죠??
먼저 태그가 form이고 클래스가 form인 요소를 가져와 submitForm 변수에 할당해줍니다.
그 submitForm 변수에 "submit" 이벤트가 발생하였을 때 !! 그 안에있는 콜백 함수들이 실행이 된다.
콜백함수를 살펴보면, e라는 매개변수를 가지고있음.
🚨 먼저, e.preventDefault()를 해준다. 해주는 이유는 form에서 "submit" 이벤트가 발생하면 기본동작으로 새로고침이 실행된다.
그래서 그 기본동작을 막기위해 사용함.
6번에서 선언한 함수 submitDiscussion 함수를 호출하여 반환값을 newDiscussion 변수에 할당한다.
그리고, 변수 newDiscussion의 값을 data.js에 있는 데이터더미 전체인 agoraStatesDiscussions에 unshift를 통해 맨앞으로 추가해준다. 3번에서 getItem으로 로컬스토리지에서 값을 받아왔었다. 기억이 나십니까?? 이제 로컬스토리지에 값을 넣는걸 해볼것이다.
localStorage.setItem("키이름","저장하려는 값") 을 통해 키이름은 discussionStorage이고, 저장하려는 값은 JSON.stringify() 메서드를 통해 문자열로 반환된 agoraStatesDiscussions 개체이다.
이렇게 localStorage에 저장을하고, 렌더링을 다시 진행하고, input 3개(author, title, question)의 값들을 모두 비워준다. 그럼 제출을 할시, 써넣은 input 입력필드의 값이 사라진다.
정리해보면, 클래스가 form인 폼에 이벤트 발생시, 먼저 새로고침을 방지하고, 6번에서의 submitDiscussion함수의 리턴값을 받아와 newDiscussion 변수에 넣어주고, 변수에 넣은값을 agoraStatesDiscussions배열에 맨앞으로 추가해준다. 그리고 agoraStatesDiscussions배열을 localStorage의 "discussionStorage" 키 아래에 저장하는데~~ 저장하기전에 !! 배열을 문자열로 변환하여 저장을 한다. 그러고 렌더링을 해주고, input 입력필드를 빈 문자열로 바꿔준다.
정리를 했는데도 이렇게 길다... ㅎㅎㅎㅎ
8. 공포의 페이지네이션... 사실 이거하려고 지금까지 어그로끈거...
142번 줄 : 한 페이지에 보여지고 싶은 게시물 개수.
143번 줄 : 142번 줄에서 지정한 개수에 따라 총 페이지개수를 구함.
144번 줄 : 첫 화면이 렌더링됐을때 1번페이지부터 시작해야하므로 1로 주었다.
145번 줄 : group은 아래 사진을보면 이전과 다음버튼 사이의 1~5까지가 group1, 6~10까지가 group2 이런식으로 묶음이라 생각.
"다은"버튼은 오타가 아니라 다은님의 영혼이 깃들어 있기때문에 해놨답니다 다은님 감사합니닷!!
146번 줄 : 한 그룹에서의 마지막번호 group1에선 5, group2에선 10이 될것이다.
147번, 148번 줄 : 그룹 한개에 5개의 페이지로 한페이지당 5개의 게시물이 등록이 되는데, 만약 총 게시물이 13개다!! 하면
페이지가 3개까지 밖에 필요가없는데 5개의 페이지를 다 실행하려면 에러가 뜨기 때문에 1~5까지가 아니라 1~3까지 페이지버튼이 나와야함. 따라서 마지막번호를 총 페이지개수로 재할당시킨다.
150번 줄 : 146번 줄과 비슷하게 한 그룹에서의 첫번째 번호를 나타낸다. 여기서 삼항연산자를 준 이유는 만약 147, 148번 줄의 예시처럼 총 게시물이 13개면 lastNum이 3이 된다. 그렇게 넘어와서 150번 줄의 lastNum에 3이 들어가면 첫번째 번호인 firstNum이 음수가 될 수 있기 때문에 음수가 되면 1이 되도록 삼항연산자를 주었다.
godDaeun함수는 event를 매개변수로 받고있다. 함수를 살펴보면,
154번 줄 : event.currentTarget.textContent는 다음 9번 내용에서 버튼들이 추가될건데, 이벤트가 발생된 버튼의 textcontent라고
생각하면된다. 다음 9번에서 자세히 설명하겠다.
155~159번 줄 : renderStart는 154번을 보면, 클릭된 버튼의 textcontent가 변수 btnNum으로 들어가있다. 만약 textcontent가 3인 버튼을 클릭하면, 한페이지당 게시물이 5개 이므로, (3-1) * 5를 해줘야 총 게시물들 배열의 10번째 인덱스부터 3페이지가 시작이 된다는 뜻이다. 다른예시로 만약, textcontent가 4인 버튼을 클릭했다면 (4-1) * 5를 통해 총 게시물들 배열의 15번째 인덱스부터 4페이지가 시작이된다.
renderEnd는 한페이지당 5개의 게시물이 있기 때문에 renderStart + 5 를 해준것이고, 그게 만약 총 게시물들 배열의 길이보다 크면,
총 게시물들 배열의 길이를 renderEnd에 할당한다. 그 이유는 만약 총 게시물이 17개가 있으면, 4페이지에선 2개의 게시물만 보여져야한다. 하지만 4페이지에선, renderEnd에 20이 할당되기 때문에 총 게시물들 배열의길이인 agoraStatesDiscussions.length보다 크게된다. 그렇게되면 20까지 돌아야되는데 17까지만 돌게되어 에러가 생기기 때문에 renderEnd를 17로 맞춰줘야한다.
9. addBtn함수는 2번처럼 createEl을 통해 HTML요소를 생성하는 함수이다.
168번 줄 : createEl을 통해 div태그, 클래스가 nav__container인 요소를 생성하여 $div 변수에 넣는다.
169번 줄 : button태그, 클래스가 prevBtn인 요소를 생성하여 $prevBtn 변수에 넣는다.
170번 줄 : $prevBtn에 클릭이벤트가 감지되면 clickPrevBtn함수를 실행한다.
171번 줄 : $prevBtn의 textContent에 "이전" 을 넣어주었다. 8번에서 설명했던 event.currentTarget.textContent가 여기서 나오는것이다. event.currentTarget은 $prevBtn가 되고, 이 $prevBtn.textContent는 "이전"이 되는 것이다.
172번 줄 : $div요소의 맨 마지막에 $prevBtn을 추가해준다.
173~175번 줄 : 위의 내용과 똑같이 반복.
176~181번 줄 : 8번의 150번 줄과 146~148번 줄에서 할당된 firstNum과 lastNum을 참조하여 firstNum부터 lastNum까지 반복하면서 btn이라는 변수에 태그가 button이고, 클래스가 pageBtn인 요소를 생성해주고 하나씩 btn.textContent를 사용하여 버튼에 번호를 먹여주고, btn에 클릭이벤트가 감지되면 godDaeun 함수가 실행되고 $div 요소의 마지막에 btn을 넣어주는 반복문을 실행한다.
182번 줄 : $div요소의 맨 마지막에 $nextBtn을 추가하고, $div를 반환한다.
10. 9번에서 생성한 이전버튼과, 다음버튼을 통해 group화 했던 페이지들을 구현해보자 !! 먼저, 다음버튼부터 봐보자!
clickNextBtn 함수는 event를 매개변수로 받고, 173~174 번 줄을 보면 알수있다시피 $nextBtn을 클릭했을때 실행되는 함수이다.
nextNum 변수에 event.currentTarget.previousElementSibling.textContent를 넣어준다. event.currentTarget.previousElementSibling.textContent는 event.currentTarget의 앞에있는 요소의 textContent이다. 즉, "다음"버튼의 앞에있는 요소의 textContent를 의미하는 것이다.
192번 줄 : 만약 위에서 설명했던 변수 nextNum이 전체페이지인 pageTotal의 값보다 크거나같으면, return으로 바로 종료하고
그렇지 않으면 currentPage에 lastNum +1을 해준다. 이유는 currentPage는 1 / 6 / 11 ... 이런식으로 나와야 하기때문이다.
198번 줄 : 145번 줄처럼 currentPage를 기준으로 그룹값을 group 변수에 할당해준다.
199번 줄 : 146번 줄처럼 group * 5 를 해주어 그룹의 마지막페이지를 lastNum에 할당해준다.
200~203번 줄 : 147, 148번 줄과 동일하게 조건문을 해준다.
204번 줄 : $nextBtn을 클릭할때마다 실행되므로 $nextBtn의 부모요소인 navWrap의 HTML 요소를 계속해서 빈문자열로 만들어줘야한다.
205번 줄 : navWrap에 자식요소로 addBtn함수의 리턴값을 추가해준다.
다음은, 이전버튼을 구현해보자!
다음버튼과 다를게 별로없다. 보게되면,
clickPrevBtn함수는 170번 줄에서 보면 $prevBtn을 클릭했을때 실행되는 함수이다.
다음 버튼과 다른점 중의 하나인 event.currentTarget.nextElementSibling.textContent 는 이전의 previousElementSibling 대신 nextElementSibling을 사용하여 event.currentTarget인 $prevBtn의 뒤에있는 요소의 textContent를 prevNum 변수에 할당하였다.
즉, prevNum 변수에 "이전" 이라는 버튼 바로뒤에있는 요소의 textContent를 할당한다는 것이다.
prevNum이 1이 아니라면 currentPage에 prevNum - 5를 한 값을 할당한다. 이전 버튼을 누르면 prevNum에는 6, 11 , 16 ... 이런식으로 들어갈 것이기때문에 -5를 하여 currentPage에 1, 6, 11 이런식이 되도록 해준다.
220번 줄 : 217, 218 줄에 따라 lastNum이 pageTotal보다 커 lastNum에 pageTotal의 값이 할당된다면 firstNum이 음수가 될수 있으므로 음수가 된다면 firstNum을 1로 해주는 삼항연산자를 사용하였다.
페이지네이션... 너무 어려운것.. 잘 가르쳐주신 동기 다은님 정말 감사합니다 !!