<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>실습 - 뉴스API</title>
<script src="./index.js" defer></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
index.js
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");
// #38074701.json -> substring(1) -> 38074701
// 상세페이지를 구분할 수 있어야 한다 - 'show'라고 이름 붙이자
// 목록페이지로 가는 경우 - 구분할 수 있는 뭔가가 필요하다 - 이름 'page'
// 페이지 번호가 계속 바뀐다-> 상태가 바뀐다(쿠키나 세션 - 리덕스)
const CONTENT_URL = "https://api.hnpwa.com/v0/item/@id.json";
// 페이징처리를 위해 필요한 상태값을 관리하는 객체(VO 느낌)
const store = {
currentPage: 1,
totalRecord: 47,
};
getData = (url) => {
ajax.open("GET", url, false);
ajax.send();
return JSON.parse(ajax.response);
};
// 글 내용을 보려면 글 목록 화면에서 제목을 클릭했을 때 (a태그가 발동되었을 때)
/* 나는 상세화면으로 간다(라우팅 -> 라우팅 구현을 라우터 함수로 정의했음
- 라우터 함수에는 조건이 3가지고, */
getNewsData = () => {
const newsData = getData(TITLE_URL);
const newsList = [];
newsList.push("<ul>");
for (let i = (store.currentPage -1)*10; i < store.currentPage*10; i++) {
// a(anchor)태그 - 상세보기(글 내용보기로 간다) - #/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>
`);
}
newsList.push("</ul>");
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>
`);
container.innerHTML = newsList.join("");
};
newsDetail = () => {
/*
지금까지는 http://localhost:1234/#38088856이었으니까
-> routerPath.substring(1)을 통해서 가져올 수 있었는데
newsDetail은 id를 추출하는게 show,page단어가 추가되어 있어서 1을 7로 바꾸어주자
다음 페이지 2번째 페이지를 보고 내용을 보면 잘 나온다
다시 목록으로 돌아가면 목록이 잘 나오는 걸 볼 수 있다
하지만 목록 보기로 왔을 때 정확히 몇번째 페이지인지 애매하다
왜냐면 바로 #으로 나왔기 때문에 그렇다
#으로 나오면 라우터 쪽에선 어떻게 동작할까? 라우터에 아무것도 없는 상태로 나올 것이다
그리고 getNewsData()로 바로 호출한다. 그럼 getNewsData함수로 가면 현재의 페이지
상태로 보이긴 할것이다
왜냐면 currentPage를 바꾸고 난 다음에 목록으로 돌아갔다가 다시 뒤로 돌아오니까
하지만 url기준으로는 몇 페이지인지 알수 없는 상황이니까
일관된 동작을 위해서 목록 화면에서 넘어갈때 즉 바로 # 으로 가지 않고
현재 currentPage를 알고 있으니까 이러한 용도로 우리가 store라고 하는 상태를
#으로 가지않고 여기다가 page를 넘겨주도록 합시다 - 85라인에 대한 설명입니다.참고하세요!!!
*/
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>
`;
};
router = () => {
const routerPath = location.hash;
if (routerPath === "") { // http://localhost:1234/#
getNewsData();
}
// 만약 여기를 #/page/ 이런 형태로 들어와 있으면 이게 0보다 크면 page라고 하는
// 해시변경이라는 걸 확인할 수 있다
else if(routerPath.indexOf('#/page/') >= 0) {
/*
currentPage에 2페이지, 3페이지 이렇게 들어가 있겠지!
페이지 뒤에 숫자값을 넣으면 되고요
그런데 우리는 아직 추출하지 않았으니까 상수로 2를 주어서 일단 동작을 확인해 봅시다
*/
store.currentPage = Number(routerPath.substring(7)); // 전역변수의 값을 변경해놓는다. 다른 함수에서도 그 값을 공유한다
getNewsData(); // 글 내용 보기
}
// 현재 페이지를 바꿔주고 새로운 페이지 값으로 그리고 getNewsData 게시글 함수를
// 다시 한 번 호출하면 됨. 게시글 함수는 현재페이지 정보를 가지고
// 리스트를 뿌리는 코드가 들어가게 됩니다
// #/show/의 경우
else {
newsDetail();
}
};
// 자바스크립트에서는 함수도 객체다
window.addEventListener("hashchange", router); // 두 번째 파라미터에서 라우팅을 위한 함수
router(); // 전처리과정
//routerPath 앞단에 page가 들어가 있으면 이건 페이징이다. 글 내용을 보는게 아니고
//그래서 routerPath라고 하는 문자열 안에 이런 문자열이 있는지 찾는 메소드가 있는데
//indexOf가 그것이다. 이메소드는 입력으로 주어진 문자열을 찾아서 있다면 0이상의
//위치값을 리턴하게 되어 있고 없다면 -1을 반환하는 함수이다.
//-1즉 0보다 작은 값이면 입력으로 주어진 문자열이 없다는 뜻이 된다
//-> #/page/ 이런 형태로 들어와 있으면 이게 0보다 크면 page라고 하는
//해시변경이라는 걸 확인할 수 있다.
<!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/55780a7e38.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 추가적으로 쌓임-->
<li class="item_row">
<!-- 8 왜 div인가? 글자, 이미지 배치를 위해 -->
<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 복사 -->
<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>
<!-- 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: 디자인에 관해서 일괄처리 위한 파일작성
-->
// 우리가 접근해야 할 DOM요소 선언하기
const items = document.querySelector('.items');
const input = document.querySelector('#footer_input');
const btnPlus = document.querySelector('.footer_button');
// arrow function으로 바꾸는 것 연습해보기
// plusEvent = () => {
function plusEvent() {
// 사용자가 입력한 텍스트를 받아온다
// 변수 선언시 : const(상수), let(변수), var(사용금지-호이스팅이슈)
const text = input.value;
console.log(text);
// 비교시 == 두개면 값만 비교하고 === 세 개면 타입도 비교
if (text === '') {
input.focus();
return;
}
// 새로운 아이템을 만든다(텍스트 + 삭제버튼)
// items 컨테이너 안에 새로 만든 아이템을 추가한다
// 인풋값을 초기화한다
input.value = '';
// 커서가 미리 가있으면 마우스 클릭하지 않고도 즉시 입력가능함
input.focus(); // 커서가 위치하게 함
// 만일 아무것도 입력하지 않고 버튼을 누르면
// 포커스를 input text로 옮기고 함수를 탈출함
}
btnPlus.addEventListener('click', () => {
// insert here
plusEvent();
});
<!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/55780a7e38.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: 디자인에 관해서 일괄처리 위한 파일작성
-->
// 우리가 접근해야 할 DOM요소 선언하기
const items = document.querySelector('.items');
const input = document.querySelector('#footer_input');
const btnPlus = document.querySelector('.footer_button');
// arrow function으로 바꾸는 것 연습해보기
// plusEvent = () => {
function 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) {
// DOM에서 제공하는 함수를 이용해서 태그를 만들 땐 li 태그를 안붙임
const itemRow = document.createElement('li');
// 태그에 속성을 추가할 땐 setAttribute 함수를 사용할 것
itemRow.setAttribute('class', 'item_row');
const item = document.createElement('div');
item.setAttribute('class', 'item');
const name = document.createElement('span');
name.setAttribute('class', 'item_name');
name.innerText = text;
const deleteBtn = document.createElement('button');
deleteBtn.setAttribute('class', 'item_delete');
deleteBtn.innerHTML = '<i class="fas fa-trash-alt"></i>';
// createitem이 호출되었을 때 화면이 먼저 완성(렌더링==DOM TREE)되고
// 되고 난 다음에야 버튼이 눌려질테니까 파라미터의 itemRow는 create 함수에서
// 생성된 전부를 이미 쥐고 있다
// html, js, css는 로컬pc에 다운로드 된 게 실행되는 것이다
deleteBtn.addEventListener('click', () => {
items.removeChild(itemRow);
});
const itemDivider = document.createElement('div');
itemDivider.setAttribute('class', 'item_divider');
item.appendChild(name);
item.appendChild(deleteBtn);
itemRow.appendChild(item);
itemRow.appendChild(itemDivider);
return itemRow;
}
btnPlus.addEventListener('click', () => {
// insert here
plusEvent();
});
// 엔터 쳤을 때도 동일(하나 - 함수로 묶어야 함)하게 처리하기
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
plusEvent();
}
});