학습 목표 : #/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>
`);
<a href = "#/page/${
store.currentPage > 1 ? store.currentPage - 1 : 1
}">이전 페이지</a>
store.currentPage
는 현재 위 전역변수로 사용된const store = {
currentPage: 1,
totalRecord: 47,
};
상수로 선언된 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가 증가하는게 아니라,
값을 유지한채 -> 목록으로 돌아가면 (부모의 자리 = 뉴스 목록을 가르킨다.)
<!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 : 디자인에 관해서 일괄처리위한 파일작성
-->
*{
/* 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 */
}
// 우리가 접근해야 할 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();
}
});