자바스크립트 - 페이징네이션 및 workout

지환·2023년 11월 2일
0

자바스크립트

목록 보기
2/4

학습 목표 : #/page/이런 형태로 들어와 있으면 이게 0보다 크면 page라고 하는 해시 변시변경이라는 걸 확인 할 수 있다.

전체코드

const ajax = new XMLHttpRequest(); //브라우저가 비동기처리를 위해 제공하는 내장객체임
const TITLE_URL = 'https://api.hnpwa.com/v0/news/1.json'; // 목록 - hashchange : 발생하지 않는다.
const container = document.getElementById('root');
const content = document.createElement('div');
//페이징처리를 위한 필요한 상태값을 관리하는 객체(VO) --> 변경이 안 된다는 특징이 있다.
//상태 정보를 객체에 저장하면, 코드 내에서 여러 함수가 이 상태를 공유하고 필요한 정보를 가져올 수 있다.

const store = {
  currentPage: 1,
  totalRecord: 47,
};

//@id로 된 부분을 JSON포맷파일에서 id값을 읽어와 치환해야 함
//#38077889 - substring(1) - 38077889
//상세페이지를 - 구분 하려면 - show로 구분해야함
//목록페이지를 - 구분 하려면 - page로 해야함
//오늘은 내가 있는 front 페이지도 기억해야되고, 다음 페이지를 눌렀을 때 이동하는 페이지도 필요하다.
//그래서 URL에다가 SHOW을 붙여서 처리한다.

//전체적인 예시로
// http://localhost:1234/#38099086 이렇게 됐으면 -> substring(1)

// http://localhost:1234/#page/1 --> substring(7) - 1페이지 번호 취둑
// http://localhost:1234/#page/2
// http://localhost:1234/#page/3

// http://localhost:1234/#show/1
// http://localhost:1234/#show/2

// 중요한 관점은 페이지 번호가 바뀐다. -> 상태가 바뀐다.(쿠키나 세션 -리덕스)
const CONTENT_URL = 'https://api.hnpwa.com/v0/item/@id.json'; // id값 -> 오늘은 페이지 번호
getData = (url) => {
  ajax.open('GET', url, false);
  ajax.send(); //send()호출될 때 비로서 서버측에 요청이 일어남
  return JSON.parse(ajax.response); //JSON->  Array
};

//뉴스목록을 가져오는 함수 구현
getNewsData = () => {
  const newsData = getData(TITLE_URL);
  const newsList = [];
  newsList.push('<ul>');
  // (store.currentPage-1) * 10; i < store.currentPage * 10 해당 알고리즘 설계를 잘 봐야된다. -> 왜 -1 했는지, 오른쪽 페이지 처리와 관련이 있다.
  for (let i = (store.currentPage - 1) * 10; i < store.currentPage * 10; i++) {
    // a - 상세보기(글 내용보기로 간다) - #/show/
    // localhost:1234/#/show/2 -----> xxx.substring(7) - Number('2') -> Integer.parseInt()
    newsList.push(`
      <li>
        <a href="#/show/${newsData[i].id}">
          ${newsData[i].title}(${newsData[i].comments_count})
        </a>
      </li>
    `);
  } //end of for
  newsList.push('</ul>');
  // 이전페이지 버튼과 다음 페이지 버튼을 만든다. 개행처리가 되니깐 블록 요소로 설계한다 -> div로
  // 이전페이지는 현재페이지 -1 이고 다음페이지는 +1 한 것이다.
  // 상태를 관리한다. ->  해당 부분을 어떻게 구현 할 것인가? -> 전역변수로 구현한다.
  newsList.push(`
    <div>
    <a href = "#/page/${
      store.currentPage > 1 ? store.currentPage - 1 : 1
      // 현재 페이지가 (store.currentPage > 1) 이 조건을 만족하면 -> 이전 페이지를 구현하기 위해서
      // store.currentPage - 1를 반환하고 (true)
      // 그게 아니라면 1를 반환한다. (false)
    }">이전 페이지</a>
    <a href = "#/page/${
      store.currentPage < 3 ? store.currentPage + 1 : 3
    }">다음 페이지</a>
    </div>
  `);
  container.innerHTML = newsList.join('');
}; //end of getNewsData()

/*
  newsDetail은 id를 추출하는게 show,page단어가 추가되어 있어서 1을 7로 바꾸어주자
  다음 페이지 2번째 페이지를 보고 내용을 보면 잘 나온다
  다시 목록으로 돌아가면 목록이 잘 나오는 걸 볼 수 있다
  하지만 목록 보기로 왔을 때 정확히 몇번째 페이지인지 애매하다
  왜냐면 바로 #으로 나왔기 때문에 그렇다
  #으로 나오면 라우터 쪽에선 어떻게 동작할까? 라우터에 아무것도 없는 상태로 나올 것이다
  그리고 getNewsData()로 바로 호출한다. 그럼 getNewsData함수로 가면 현재의 페이지
  상태로 보이긴 할것이다
  왜냐면 currentPage를 바꾸고 난 다음에 목록으로 돌아갔다가 다시 뒤로 돌아오니까
  하지만 url기준으로는 몇 페이지인지 알수 없는 상황이니까
  일관된 동작을 위해서 목록 화면에서 넘어갈때 즉 바로 # 으로 가지 않고 
  현재 currentPage를 알고 있으니까 이러한 용도로 우리가 store라고 하는 상태를 
  #으로 가지않고 여기다가 page를 넘겨주도록 합시다 - 85라인에 대한 설명입니다.참고하세요!!!
   <div><a href="#/page/${store.currentPage}">목록으로</a></div> */

newsDetail = () => {
  //#38088856
  const id = location.hash.substring(7);
  const ncontent = getData(CONTENT_URL.replace('@id', id));
  container.innerHTML = `
  <h1>${ncontent.title}</h1>
  <div><a href="#/page/${store.currentPage}">목록으로</a></div>
`;
  // getNewsData기 때문에 show가 아니라 page로 가야된다.
}; ////////end of newsDetail

router = () => {
  //실제로 목록을 누르면 http://localhost:57327/# 를 갖는데
  //location.hash에 #만 있으면 빈값을 반환함
  const routerPath = location.hash;
  if (routerPath === '') {
    //==두개면 값만 비교하는데 세개면 타입까지도 비교함
    getNewsData(); //글 목록 보기
  } else if (routerPath.indexOf('#/page/') >= 0) {
    // routerPath 앞단에 page가 들어있으면 페이징 처리이다. -> 글 내용을 보는게 아니다.
    // routerPath문자열 안에 '#/page/' 를 찾는 indexOf 메소드가 있고 -> 입력으로 주어지는 문자열을 찾아서
    // 있다면 0이상의 위치값을 리턴 / 없다면 -1리턴하는 함수다.
    // 해당 값에서 만약에 있으면 0보다 큰 값을 얻고 그걸 해시의 변동이 일어나게 되고
    // 그걸 hashchange가 감지하여 그걸 page||show 둘 중에 하나로 선택한다.
    // #/page/있는 부분이 routerPath에 있다면 #시작점--> 위치를 반환 할 것이고
    // 그게 >=0보다 크다면 해당 else if문을 실행하는것이다. 라고 이해하면 되는것인가? - > yes(검증완료)
    /*
    currentPage에 2페이지, 3페이지 이렇게 들어가 있겠지, 페이지 뒤에 숫자값을 넣으면 된다.
    그런데 우리는 아직 추출하지 않았으니깐 상수로2를 주어서 일단 동작하게 해보자
    */
    store.currentPage = Number(routerPath.substring(7)); // 전역변수의 값을 변경해놓는다. 다른 함수에서도 그 값을 공유한다.
    getNewsData(); // 글 내용 보기
  }
  // 학습 목표 : #/page/이런 형태로 들어와 있으면 이게 0보다 크면 page라고 하는 해시 변시변경이라는 걸 확인 할 수 있다.
  // indexof 참고하기(Array.proto)
  //#/show/의 경우 Detail를 타게된다.

  // https://gent.tistory.com/462 문자열 참고
  else {
    newsDetail(); //글 내용 보기
  }
};

window.addEventListener('hashchange', router);
router();

/*
상세보기로 갈 땐 , start지점인 getNewsData를 바꿔야 하고 -> 상세보기에서 -> 목록으로 갈 땐 page를 바꿔야 된다. 
글 내용을 보려면 : 글 목록 화면에서 제목을 클릭 했을 때 (a태그가 발동 되었을 때)

- 상세 화면으로 간다 -> routing을 생각해야 되고 -> router -> 조건이 3가지가 있다. -> 그 중에 3번째가 상세기 떄문에 -> Detail로 간다.

*/

문자열 참고

주석 뺀 버전

const ajax = new XMLHttpRequest(); //브라우저가 비동기처리를 위해 제공하는 내장객체임
const TITLE_URL = 'https://api.hnpwa.com/v0/news/1.json'; // 목록 - hashchange : 발생하지 않는다.
const container = document.getElementById('root');
const content = document.createElement('div');
//페이징처리를 위한 필요한 상태값을 관리하는 객체(VO) --> 변경이 안 된다는 특징이 있다.
//상태 정보를 객체에 저장하면, 코드 내에서 여러 함수가 이 상태를 공유하고 필요한 정보를 가져올 수 있다.

const store = {
  currentPage: 1,
  totalRecord: 47,
};

const CONTENT_URL = 'https://api.hnpwa.com/v0/item/@id.json'; // id값 -> 오늘은 페이지 번호
getData = (url) => {
  ajax.open('GET', url, false);
  ajax.send(); //send()호출될 때 비로서 서버측에 요청이 일어남
  return JSON.parse(ajax.response); //JSON->  Array
};

//뉴스목록을 가져오는 함수 구현
getNewsData = () => {
  const newsData = getData(TITLE_URL);
  const newsList = [];
  newsList.push('<ul>');
  // (store.currentPage-1) * 10; i < store.currentPage * 10 해당 알고리즘 설계를 잘 봐야된다. -> 왜 -1 했는지, 오른쪽 페이지 처리와 관련이 있다.
  for (let i = (store.currentPage - 1) * 10; i < store.currentPage * 10; i++) {
    // a - 상세보기(글 내용보기로 간다) - #/show/
    // localhost:1234/#/show/2 -----> xxx.substring(7) - Number('2') -> Integer.parseInt()
    newsList.push(`
      <li>
        <a href="#/show/${newsData[i].id}">
          ${newsData[i].title}(${newsData[i].comments_count})
        </a>
      </li>
    `);
  } //end of for
  newsList.push('</ul>');

  newsList.push(`
    <div>
    <a href = "#/page/${
      store.currentPage > 1 ? store.currentPage - 1 : 1
      // 현재 페이지가 (store.currentPage > 1) 이 조건을 만족하면 -> 이전 페이지를 구현하기 위해서
      // store.currentPage - 1를 반환하고 (true)
      // 그게 아니라면 1를 반환한다. (false)
    }">이전 페이지</a>
    <a href = "#/page/${
      store.currentPage < 3 ? store.currentPage + 1 : 3
    }">다음 페이지</a>
    </div>
  `);
  container.innerHTML = newsList.join('');
}; //end of getNewsData()



newsDetail = () => {
  //#38088856
  const id = location.hash.substring(7);
  const ncontent = getData(CONTENT_URL.replace('@id', id));
  container.innerHTML = `
  <h1>${ncontent.title}</h1>
  <div><a href="#/page/${store.currentPage}">목록으로</a></div>
`;
  // getNewsData기 때문에 show가 아니라 page로 가야된다.
}; ////////end of newsDetail

router = () => {
  //실제로 목록을 누르면 http://localhost:57327/# 를 갖는데
  //location.hash에 #만 있으면 빈값을 반환함
  const routerPath = location.hash;
  if (routerPath === '') {
    //==두개면 값만 비교하는데 세개면 타입까지도 비교함
    getNewsData(); //글 목록 보기
  } else if (routerPath.indexOf('#/page/') >= 0) {
   
    store.currentPage = Number(routerPath.substring(7)); // 전역변수의 값을 변경해놓는다. 다른 함수에서도 그 값을 공유한다.
    getNewsData(); // 글 내용 보기
  }


  else {
    newsDetail(); //글 내용 보기
  }
};

window.addEventListener('hashchange', router);
router();


이전페이지 구현

이전 페이지를 눌렀을 때, 어떤 함수가 동작하는가?

//뉴스목록을 가져오는 함수 구현
getNewsData = () => {
  const newsData = getData(TITLE_URL);
  const newsList = [];
  newsList.push('<ul>');
  // (store.currentPage-1) * 10; i < store.currentPage * 10 해당 알고리즘 설계를 잘 봐야된다. -> 왜 -1 했는지, 오른쪽 페이지 처리와 관련이 있다.
  for (let i = (store.currentPage - 1) * 10; i < store.currentPage * 10; i++) {
    // a - 상세보기(글 내용보기로 간다) - #/show/
    // localhost:1234/#/show/2 -----> xxx.substring(7) - Number('2') -> Integer.parseInt()
    newsList.push(`
      <li>
        <a href="#/show/${newsData[i].id}">
          ${newsData[i].title}(${newsData[i].comments_count})
        </a>
      </li>
    `);
  } //end of for
  newsList.push('</ul>');

  newsList.push(`
    <div>
    <a href = "#/page/${
      store.currentPage > 1 ? store.currentPage - 1 : 1
      // 현재 페이지가 (store.currentPage > 1) 이 조건을 만족하면 -> 이전 페이지를 구현하기 위해서
      // store.currentPage - 1를 반환하고 (true)
      // 그게 아니라면 1를 반환한다. (false)
    }">이전 페이지</a>
    <a href = "#/page/${
      store.currentPage < 3 ? store.currentPage + 1 : 3
    }">다음 페이지</a>
    </div>
  `);
  container.innerHTML = newsList.join('');
};
  • 해당 함수에서
newsList.push(`
    <div>
    <a href = "#/page/${
      store.currentPage > 1 ? store.currentPage - 1 : 1
    }">이전 페이지</a>
    <a href = "#/page/${
      store.currentPage < 3 ? store.currentPage + 1 : 3
    }">다음 페이지</a>
    </div>
  `);

  • 이 부분이다. 앵커태그를 통해서 http://localhost:1234/#/page/1 해당 부분이 실행된다. 근데 왜 이전 페이지로 돌아가지 않는가?

<a href = "#/page/${
      store.currentPage > 1 ? store.currentPage - 1 : 1
    }">이전 페이지</a>
  • 이 부분 때문이다. store.currentPage는 현재 위 전역변수로 사용된

const store = {
  currentPage: 1,
  totalRecord: 47,
};

이 객체를 생성하여 마치 자바에서 VO(불변객체)를 사용하는 것과 같은 원리다. 즉,

상수로 선언된 store는 -> currentPage와 totalRecord를 사용 할 수 있다는 것이다 (Key&Vaule)로 (store.currentPage 는 = 1 의미 || store.totalRecord 는 = 47로 의미) store는 소유주다.

그리고 삼항연산자로 조건을 분기하여 a태그 내에서 조건을 걸어준 것이다.

Test Case

store.currentPage => 

1 > 1 ? store.currentPage -1 : 1 --> false 도출 하기 때문에 오른쪽 1 값 나옴 

그렇기 떄문에 이전페이지로 이동하지 않는 것이다. a태그 조건 분기 자체가 성립이 안됨

다음페이지

그렇다면 이와 같은 논리로 다음페이지를 펼쳐 볼 수 있지 않을까?

하지만 다음페이지는 우리가 처음에 객체를 선언한 store 부분에 totalRecord 을 보게 되면 이 부분이 페이지의 끝 limit이라고 보면 된다.

그러면 이 부분에 대한 알고리즘은 어디서 적용 했는가? 라고 할 수 있다. 필자도 이 글을 작성하면서 이런 의문점이 들었다.

무슨 기준으로 이 페이지를 나눠서 limit을 주지?

전체 페이지를 나누는 알고리즘은 for문에서 볼 수 있다.

for (let i = (store.currentPage - 1) * 10; i < store.currentPage * 10; i++) {
    // a - 상세보기(글 내용보기로 간다) - #/show/
    // localhost:1234/#/show/2 -----> xxx.substring(7) - Number('2') -> Integer.parseInt()
    newsList.push(`
      <li>
        <a href="#/show/${newsData[i].id}">
          ${newsData[i].title}(${newsData[i].comments_count})
        </a>
      </li>
    `);
  } //end of for
  newsList.push('</ul>');

해당 for문을 통해서 전체적인 페이지의 골자(구조)를 나타내고 있다는 것을 직관적으로 알 수 있다.

그렇다면 for문에 들어가 있는 조건을 왜 저렇게 줬는지 이해가 안 갈 것이다. -> 필자도 그랬음

우리는 store.currentPage(현재 페이지)를 1로 설정한 것을 알 수 있다. 그렇다면

const store = {
  currentPage: 1,
  totalRecord: 47,
};


else if (routerPath.indexOf('#/page/') >= 0) {
    store.currentPage = Number(routerPath.substring(7));
    //  페이지 간의 전환을 관리
    // 전역변수의 값을 변경해놓는다. 다른 함수에서도 그 값을 공유한다.
    getNewsData(); // 글 내용 보기
  }

해당 내용을 복습하며 정리

페이징 처리에 대해서 집중하자.
1. 우선은 이전페이지와 다음페이지가 구현되어있다. 

router를 통해서 전체적인 움직임이 결정되고 중간중간 함수가 구현되어 있다.

우선 총 3가지로 볼 수 있다.

1. routerPath === ''
2. routerPath.indexOf('#/page/') >= 0 일 때, 
3. 글 상세보기 일 때,3가지로 조건을 분기했다. 이 역시 해시체인지를 이용하고(이벤트 처리) 해당 부분은 라우터에서 처리한다.
다른 말로 하면, 라우터가 가지고 있는 해시가 변화되면 해시체인지가 이 부분을 인지해서 -> 이벤트 처리를 진행한다.

- routerPath === ''일 땐, 빈 문자열이기 때문에 getNewsData()로 글 목록을 본다.

-> 그렇다면 getNewsData 함수를 보자. 핵심적인 로직 및 알고리즘은 생략하고 전체적인 큰 틀로 접근하겠다.

-> for문을 통해 해당 페이지에 뉴스 목록을 작성하고, <a>태그를 이용하여 newData[i].id를 꺼내오는데 여기서 중요한 포인트는
"#/show/${newsData[i].id}이다. -> JSON 배열로 반환된 아이디를 {Key : Value} 값으로 꺼내온다.

-> 그리고 #/show/ 이 부분도 페이징처리를 하는데 있어 중요한 부분이다. 이전에는 #으로 구현해서 정확히 몇번째 페이지인지 몰랐지만, show/page를 사용해서
해당 페이지에 대해서 해당 페이지가 어디 페이지에 해당 되는지 알 수 있게 되었다. [매우 중요한 부분]

예를 들면 2page 누르고 -> show하면(Detail)누르면 -> 2page라는 것을 쥐고 있다. 어떻게? -> show라는걸 붙이고 store.currentPage를 이용해서.


-> 그리고 2번째 핵심적인 로직은 
   <a href = "#/page/${ store.currentPage > 1 ? store.currentPage - 1 : 1}
  <a href = "#/page/${  store.currentPage < 3 ? store.currentPage + 1 : 3 }">이다.
-> 이 부분을 보게 되면 조건문 분기를 통해 해당 부분에 조건을 만족하면 -> +1 되고 -1 되는 부분은
    직관적로 알 수 있다. 간단명료하게 설명하자면, 이전페이지니깐 -1 || 다음페이지깐 +1 하는 것이다.
-> 근데!!! 앞에 page를 붙여서 지금 페이지네이션을 하는 것이다.
(페이지네이션 : 콘텐츠를 여러 페이지로 나누어 다음 또는 이전 페이지로 이동하거나 특정 페이지로 이동할 수 있는 요소)
--> 그리고 여기서 더하고(+)값들은(-) 해당 전역변수로 및 VO객체로 취급하는 store.currentPage에 값이 반영된다.
이 부분이 매우 중요하다. 그래서 우리가 다음페이지를 누르면 값이 변화 하는 것이다. 

--> 여기서 한 가지 더 : 밑에 store.currentPage = Number(routerPath.substring(7));
부분을 수행하는 시점을 보면, 아까 위에서 언급한 <a></a>태그보다 뒤 라는 점을 꼭!!! 알고 있자.

-->✔✨위에서 변화된 객체의 값을 밑에서 슬라이싱으로 짤라서
 store.currentPage에 넣는 것이고(&&) 
그 값을 getNewData( 글 내용보기)에 반영 하는 것이다.✔✨

-- subString 동작 원리 :  해시 기호(#)부터 URL의 끝까지의 문자열
index.html#/page/2라고 가정하면 -> /page/2
# : 0 -> / : 1 -> p : 2 -> a : 3 -> g : 4 -> e : 5 -> / : 6 -> 2 : 7 ✔✔ 이 부분

--> 여기서까지 이해 했다면, 잘 따라왔다. -> 또 한 번 시점의 문제를 짚고 넘어가려한다.
--> store.currentPage = Number(routerPath.substring(7)); 를 먼저 실행 -> getNewsData(); 글내용보기를 한다.
--> 잘 생각해보자. 왜 이렇게 로직을 짰을까? 
--> 전역변수의 값을 변경해 놓을 시점이 글 내용 보기와 일치하는 시점인가? 라는 질문이다.
--> 만약에 store.currentPage가 2이고 -> getNewsData();를 실행하면 2인 상태로 for문이 돌고 그 값을 가지고
--> 다시 [이전페이지] [다음 페이지]를 만들고 이 만든 시점은  store.currentPage가 3이다.

3. 마지막 글 상세보기 일 때, 

해당 부분에서 중요한 건  <div><a href="#/page/${store.currentPage}">목록으로</a></div>.

목록으로 돌아가는건 다시 원래 (부모의 자리로) 상세보기를 눌렀을 때 store.currentPage가 증가하는게 아니라,
값을 유지한채 -> 목록으로 돌아가면 (부모의 자리 = 뉴스 목록을 가르킨다.)



WorkOut.html 코드

    <!DOCTYPE html>
    <html lang="ko">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Workout - 실습</title>
        <script src="https://kit.fontawesome.com/3b3af91d34.js" crossorigin="anonymous"></script>
        <link rel="stylesheet" href="workout.css">
        <script src="workout.js" defer></script>
    </head>
    <body>
        <!-- 1 전체테두리 -->
        <section class="list">
            <!-- 2 제목 -->
            <header class="header">Workout</header>
            <!-- 3 ul body -->
            <ul class="items">
                <!-- 4 추가적으로 쌓임 -->                        
            </ul>
            <!-- 5 바닥 - 플러스버튼 추가  -->
            <footer class="footer">
                <!-- 6. 운동명 입력받는 콤포넌트 -->
                <input type="text" id="footer_input">
                <!-- 7. 추가버튼  -->
                <button class="footer_button">
                    <i class="fas fa-plus"></i>
                </button>
            </footer>
        </section>    
    </body>
    </html>
    <!-- 
        javascript :  DOM 조작하기 위해서 JS활용해 본다.(목표)
        css : 디자인에 관해서 일괄처리위한 파일작성
    -->

WorkOut.css

*{
    /* 1. padding 이나 border사이즈가 width나 height포함 */
    box-sizing: border-box;
    /*border: 1px solid red; 2 선긋기 - 안쪽여백, 밖여백 */
}

ul {
    padding: 0;/* 3 안쪽여백없앰 - 구슬도 사라짐 */
}

body {
    background-color: #ced3df;/* 5 배경색  */
    text-align: center;/* 4 컨텐츠가 중앙 */
}

.list {
    width: 400px;/* 6 컨텐츠 폭크기 */
    margin: auto;/* 7. 블록요소라 마진이 오른쪽으로만 들어감 */
    background-color: #f1f0f7;/* 8 */
    border-radius: 20px;/* 9 모서리 굴림 */
}

.header {
    height: 48px;/* 10 글자높이 */
    padding: 8px;
    font-size: 24px;/* 11 글자 크기*/
    background: rgb(2, 0, 36);/* 12 */
    background: linear-gradient(
        90deg,
        rgba(2, 0, 36, 1) 0%,
        rgba(212, 94, 196, 1) 51%,
        rgba(0, 212, 255, 1) 100%
      );
    border-radius: 20px 20px 0 0;/* 13 */
    color: white;/* 14 */
}

.footer {
    background: rgb(2, 0, 36);/* 15 */
    background: linear-gradient(
        90deg,
        rgba(2, 0, 36, 1) 0%,
        rgba(212, 94, 196, 1) 51%,
        rgba(0, 212, 255, 1) 100%
      );   
    border-radius: 0 0 20px 20px ;/* 16 */
}

.items {
    height: 500px;/* 17 ul */
    overflow-y: auto;/* 18 아이템이 많으면 자동스크롤바 생성 */
}

.item {
    display: flex;/* 19 글자와 쓰레기통 간격 벌리기 */
    justify-content: space-between;/* 20 */
    padding: 8px 32px;/* 21 양쪽 끝 내부여백주기 */
}

.item_divider {
    width: 90%;/* 22 선긋기 */
    height: 2px;/* 23 선두께 */
    background-color: lightgray;/* 24 선색상 */
    margin: auto;/* 25 안주면 오른쪽 모두 공백처리 */
}

button {
    outline: none;/* 26 안쪽 선제거 */
    border: none;/* 27 버튼 테두리 선 제거 */
    background: transparent;/* 28 버튼배경색을 바탕색으로 */
    cursor: pointer;/* 29 손 */
}

/* 가상선택자 사용시 :한개도 ::두개도 가능 */
.item_delete:hover {
    color: red;/* 30 가상선택자로 마우스 오버시 빨강 */
    transform: scale(1.1);/* 31 오버시 휴지통 10%크게 */
}

.item_delete {
    font-size: 16px;/* 32 휴지통 크기 */
    transition: all 300ms ease-in;/* 33 애니메이션 속도조절 */
}

.footer_button {
    color: white;/* 34 - 버튼 라벨 흰색 */
    width: 48px;/* 35 */
    height: 48px;/* 36 */
    background-color: black;/* 37 */
    font-size: 28px;/* 38 */
    border-radius: 50%;/* 39 동그랗게 */
}

.footer_button:hover {
    transform: scale(1.1);/* 40 플러스버튼 오버시 크게 */
    transition: transform 300ms ease-in;/* 41 애니메이션 */
}
/* 운동이름 입력창 */
#footer_input {
    width: 100%;/* 42 입력창 꽉차게 그랬더니 버튼이 밀림 */
    height: 32px;/* 43 */
    border: none;/* 44 */
    font-size: 24px;/* 45 */
    padding: 0 16px;/* 46 위아래0 좌우 16 */
}

WorkOut.js

// 우리가 접근해야 할 DOM요소 선언하기
const items = document.querySelector('.items');
const input = document.querySelector('#footer_input');
const btnPlus = document.querySelector('.footer_button');

plusEvent = () => {
  // 사용자가 입력한 텍스트를 받아온다
  // 변수 선언시 : const(상수), let(변수), var(사용금지-호이스팅이슈)
  const text = input.value;
  console.log(text);
  //비교시 == 두개이면 값만 비교하고 ===세개이면 타입도 비교함
  if (text === '') {
    input.focus();
    //이 함수를 호출하면 해당 입력 요소로 커서(포커스)가 이동하며,
    //사용자가 바로 입력을 시작
    return;
  }
  //새로운 아이템을 만든다(텍스트 + 삭제버튼)
  const item = createItem(text);
  //items컨테이너안에 새로 만든 아이템을 추가한다
  items.appendChild(item);
  //새로 추가된 아이템으로 스크롤링을 함
  item.scrollIntoView({ block: 'center' });
  //인풋값을 초기화 한다
  input.value = '';
  //커서가 미리 가있으면 마우스 클릭하지 않고도 즉시 입력가능함
  input.focus(); //커서가 위치하게함
  //만일 아무것도 입력하지 않고 버튼을 누르면
  //포커스를 input text로 옮기고 함수를 탈출함
  //어떤 요소를 클릭하지 않아도 페이지가 로드될 때 해당 입력 상자에 커서(포커스)가 위치하게한다.
};

function createItem(text) {
  const itemRow = document.createElement('li');
  /* DOM제공하는 함수를 이용해서 태그를 만들 땐 li태그를 */
  itemRow.setAttribute('class', 'item_row');
  /* 태그에 속성을 추가할땐 setAttribute함수를 사용할것 */
  const item = document.createElement('div');
  item.setAttribute('class', 'item');

  const name = document.createElement('span');
  name.setAttribute('class', 'item_name');
  name.innerText = text;
  // text 변수의 값을 텍스트로 설정합니다. 즉, name 요소에는 text 변수의 값이 표시된다.

  const deleteBtn = document.createElement('button');
  deleteBtn.setAttribute('class', 'item_delete');
  deleteBtn.innerHTML = '<i class="fas fa-trash-alt"></i>';
  //createItem이 호출되었을 때 화면이 먼저 완성(렌더링==DOM TREE)되고
  //난 다음에야 버튼이 눌려질테니까 파라미터의 itemRow는 createItem함수에서
  //생성된 전부를 이미 쥐고 있다
  //html, js, css는 로컬 PC에 다운로드 된게 실행되는 것이다.
  deleteBtn.addEventListener('click', () => {
    items.removeChild(itemRow); // li를 제거한다 라고 보면 된다.
  });

  const itemDivider = document.createElement('div');
  itemDivider.setAttribute('class', 'item_divider');
  item.appendChild(name); //
  item.appendChild(deleteBtn); //

  itemRow.appendChild(item);
  //<li><div><div></div></div></li>
  itemRow.appendChild(itemDivider);

  /**
   * <body>
    <ul class="items">
        <!-- 첫 번째 리스트 아이템 -->
        <li class="item_row">
            <div class="item">
                <span class="item_name">첫 번째 아이템</span>
                <button class="item_delete">
                    <i class="fas fa-trash-alt"></i>
                </button>
            </div>
            <div class="item_divider"></div>
        </li>
        <!-- 두 번째 리스트 아이템 -->
        <li class="item_row">
            <div class="item">
                <span class="item_name">두 번째 아이템</span>
                <button class="item_delete">
                    <i class="fas fa-trash-alt"></i>
                </button>
            </div>
            <div class="item_divider"></div>
        </li>
        <!-- 여러 리스트 아이템들을 나열할 수 있습니다 -->
    </ul>
</body>
</html>
   */
  return itemRow;
}

btnPlus.addEventListener('click', () => {
  //insert here
  plusEvent();
});

//엔터 쳤을 때도 동일(하나 - 함수로 묶어야 한다.)하게 처리하기 -
input.addEventListener('keypress', (e) => {
  if (e.key === 'Enter') {
    plusEvent();
  }
});

profile
아는만큼보인다.

0개의 댓글