(28)-23.11.01[JS]

DANA·2023년 11월 1일

KDT-구디아카데미

목록 보기
28/56

코딩 공부할 수 있는 사이트
https://www.w3schools.com/

XMLHttpRequest 설명
https://developer.mozilla.org/ko/docs/Web/API/XMLHttpRequest

<작업지시서>

  • 라우터 구현: 함수형 처리를 해보자
  • 페이징처리: 함수설계해서 재사용성 높이기
  • 라우터: 서로 다른 네트워크 사이에 통신경로를 지정하고, 데이터의 전송을 중계하는 장치
  • 페이징 처리 (Pagination): 대량의 데이터나 긴 목록을 여러 페이지로 나누어 표시하는 방법

화면 처리를 위해서 함수 2개를 설계
(화면을 두 가지로 나누자)

1. 목록보기
function getNewsData(){}
URL - TITLE_URL
window.addEventListener('hashchange', function(){})

2. 내용보기 - function newsDetail(){}
: 사용자가 선택한 뉴스 제목에서 해시값(id)을 가져와서
newsList[i].id
CONTENT_URL
http://localhost:1234/XXX/#3809991.json

step5

목록과 내용 화면이 같은 화면으로 보인다 -> append
왜 같은 화면에 동시에 표시되는지 살펴볼까?

DOM API를 사용하여 페이지를 그렸더니 직관적(DOM TREE)이지 않아서
유지보수하는데 불편할 것 같다.
이것을 개선하고 싶은데 그렇게 하려면 오히려 DOM API를 사용하지 않는다.
대신 문자열을 이용해서 태그를 이어 붙이면 코드 양은 늘어나지만
오히려 복잡도는 낮출수도 있지 않나?

전체적인 구조를 좀 바꿔볼까?
createElement, appendChild 가능한 다 걷어낸다
*/

step5-index.html

<!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>

step5-index.js

const ajax = new XMLHttpRequest();
  
const TITLE_URL = "https://api.hnpwa.com/v0/news/1.json";
const container = document.getElementById("root");
const content = document.createElement("div");
// @id로 된 부분을 JSON포맷파일에서 id값을 읽어와 치환해야 함
const CONTENT_URL = "https://api.hnpwa.com/v0/item/@id.json";

getData = (url) => {
  ajax.open("GET", url, false);
  ajax.send();
  return JSON.parse(ajax.response);
};

const newsList = getData(TITLE_URL);
console.log(newsList);
const ul = document.createElement("ul");

addEventListener("hashchange", (event) => {
  const id = location.hash.substring(1);
  const ncontent = getData(CONTENT_URL.replace("@id", id));

  container.innerHTML = `
    <h1>${ncontent.title}</h1>
    <div><a href="#">목록으로</a></div>
  `;
});

// 배열 선언(빈 배열)
const newsPage = []; 
// newsPage 배열에 새로운 항목을 추가
newsPage.push("<ul>");
// 상위 10개의 뉴스 항목을 반복하며 목록으로 나타냄
for (let i = 0; i < 10; i++) {
  newsPage.push(`
    <li>
      <a href = "#${newsList[i].id}">
        ${newsList[i].title}(🖱️${newsList[i].comments_count})
      </a>
    </li>
  `);
}
newsPage.push("</ul>");

container.innerHTML = newsPage.join("");
const id = location.hash.substring(1);
  • location.hash -> #33899902 :location(내장객체).hash(location이 제공하는 속성)
container.innerHTML = `
    <h1>${ncontent.title}</h1>
    <div><a href="#">목록으로</a></div>
  `;
  • 선택한 특정 뉴스 항목의 내용을 표시
  • ${ncontent.title}: 선택한 뉴스 항목의 제목을 출력
  • "목록으로" 링크를 통해 다시 뉴스 목록으로 돌아갈 수 있도록 함
const newsPage = [];
  • 빈 배열을 생성하고, 이 빈 배열을 newsPage 변수에 할당
  • 이후 코드에서 newsPage 배열은 배열 요소들을 저장하거나 조작하는 데 사용
  • 뉴스목록을 배열에 담아서 한 번에 root 안에 집어넣어준다
    // append 즉, 추가하지 않고 새로고침이 된 것처럼 덮어쓴다 -> 뭐가 이렇다는 거지?

join( ) 메서드

  • 배열의 모든 요소를 하나의 문자열로 결합
  • 배열 요소 사이에 특정 구분자(separator)를 삽입하여 요소들을 연결
ex) 다음과 같은 배열이 있다면
const fruits = ["apple", "banana", "cherry"];
    // 1. 구분자로 쉼표와 공백을 사용(디폴트)
  	array.join(", ")
  	출력: "apple, banana, cherry"
  	// 2. 구분자 없이 사용했을 때
	array.join()
	출력: "applebananacherry"
container.innerHTML = newsPage.join("");
  • join(""): 여기서는 콤마로 구분자가 필요하지 않기 때문에 빈 문자열로 대신함

함수 추가하는 이유 - 라우터에서 재사용하기 위해

// 해커뉴스로부터 데이터를 가져오기
// 파라미터: url
// 리턴타입: Array
getData(url) -> JSON -> Array
https://api.hnpwa.com/v0/news/1.json
https://api.hnpwa.com/v0/item/@id.json
https://api.hnpwa.com/v0/item/#38077891.json
https://api.hnpwa.com/v0/item/#38077891.json

getNewsData(파라미터 없고): 리턴타입도 없다
리턴타입이 없는데 어떻게 출력이 되지?
배열을 선언해서 화면에 그려질 태그를 직접 문자열로써 작성함
백틱, 즉 템플릿리터럴 이용해서 읽어온 데이터를 직접 추가함
별도로 리턴타입에 대한 설계가 필요없다

결론: 읽어들인 정보를 함수 안에서 직접 그리니까 리턴 필요 없다

  1. 배열을 선언해서 마치 ArrayList처럼 맨 뒤에 차례대로 붙여주는 함수 push 사용하였다.
    배열에 있는 데이터를 꺼내서 태그를 문자열로 작성함 - join('') 사용

결론: 배열에 있는 데이터를 문자열로 전환했고 그걸 동일하게 innerHTML 사용하여 root 태그에 삽입(div와 div 사이에)하였다

step6

index.js-ver.1

  • // 뉴스 목록 데이터 가져오기뉴스 타이틀을 눌렀을 때 콘솔에 #아이디가 찍힌다
  • 뉴스 목록을 가져오는 함수 구현
const ajax = new XMLHttpRequest();
  
const TITLE_URL = "https://api.hnpwa.com/v0/news/1.json";
const container = document.getElementById("root");
const content = document.createElement("div");
const CONTENT_URL = "https://api.hnpwa.com/v0/item/@id.json";

getData = (url) => {
  ajax.open("GET", url, false);
  ajax.send();
  return JSON.parse(ajax.response);
};

// 뉴스목록을 가져오는 함수 구현
getNewsData = () => {
  // 뉴스 목록 데이터 가져오기
  const newsData = getData(TITLE_URL);
  // 뉴스 목록을 저장할 배열
  const newsList = [];
  // HTML 목록 시작 태그 추가
  newsList.push("<ul>");
  // 상위 10개 뉴스 항목을 반복하며 목록 생성
  for (let i = 0; i < 10; i++) {
    newsList.push(`
      <li>
        <a href = "#${newsData[i].id}">
          ${newsData[i].title}(🖱️${newsData[i].comments_count})
        </a>
      </li>
    `);
  }
  newsList.push("</ul>");
  // 뉴스 목록을 "root" 요소에 추가
  container.innerHTML = newsList.join("");
}; // end of getNewsData()

// 뉴스 항목을 표시하는 함수
newsDetail = () => {
  // 현재 URL의 해시값을 가져옴
  const id = location.hash;
  // 해시값을 콘솔에 출력 (페이지 내비게이션에 사용될 것)
  console.log(id);
  /*  
  const ncontent = getData(CONTENT_URL.replace("@id", id));
  container.innerHTML = `
  <h1>${ncontent.title}</h1>
  <div><a href="#">목록으로</a></div>
`;
*/
}; // end of newsDetail

// 페이지 내비게이션을 위한 라우터 함수(아직 구현되지 않음)
router = () => {};

// "hashchange" 이벤트 발생 시 newsDetail 함수 실행
window.addEventListener("hashchange", newsDetail);

// 초기 페이지 로드 시 뉴스 목록 가져오기
getNewsData();

index.js-ver.2

  • 뉴스 타이틀을 클릭하면 덮어쓰기로
const ajax = new XMLHttpRequest();

const TITLE_URL = "https://api.hnpwa.com/v0/news/1.json";
const container = document.getElementById("root");
const content = document.createElement("div");
const CONTENT_URL = "https://api.hnpwa.com/v0/item/@id.json";

getData = (url) => {
  ajax.open("GET", url, false);
  ajax.send();
  return JSON.parse(ajax.response);
};

// 뉴스목록을 가져오는 함수 구현
getNewsData = () => {
  const newsData = getData(TITLE_URL);
  const newsList = [];
  newsList.push("<ul>");
  for (let i = 0; i < 10; i++) {
    newsList.push(`
      <li>
        <a href = "#${newsData[i].id}">
          ${newsData[i].title}(🖱️${newsData[i].comments_count})
        </a>
      </li>
    `);
  }
  newsList.push("</ul>");
  container.innerHTML = newsList.join("");
}; // end of getNewsData()

newsDetail = () => {
  // 눌렀을 때 콘솔에서 #38092612
  const id = location.hash.substring(1);
  const ncontent = getData(CONTENT_URL.replace("@id", id));
  container.innerHTML = `
  <h1>${ncontent.title}</h1>
  <div><a href="#">목록으로</a></div>
`;
}; // end of newsDetail

router = () => {};

window.addEventListener("hashchange", newsDetail);
getNewsData();

index.js-ver.3

타이틀을 누르고 내용에서 목록으로 누르면 다시 목록으로

// XMLHttpRequest 인스턴스 생성
const ajax = new XMLHttpRequest();

// 뉴스 목록과 상세 내용을 표시할 HTML 요소 선택
const TITLE_URL = "https://api.hnpwa.com/v0/news/1.json";
const container = document.getElementById("root");
const content = document.createElement("div");
const CONTENT_URL = "https://api.hnpwa.com/v0/item/@id.json";

// getData 함수 정의
// 주어진 URL로 비동기적인 GET 요청을 보내고 응답을 비동기적으로 기다림
getData = (url) => {
  ajax.open("GET", url, true);
  ajax.send();
  // 응답이 도착하면 이를 JSON 형식으로 파싱하여 반환
  return JSON.parse(ajax.response);
};

// getNewsData(뉴스목록을 가져오는) 함수 구현
getNewsData = () => {
  // getData 함수를 사용해 뉴스 목록 데이터 가져오기
  const newsData = getData(TITLE_URL);
  const newsList = [];
  
  newsList.push("<ul>");
  
  for (let i = 0; i < 10; i++) {
    // 뉴스 목록 생성
    newsList.push(`
      <li>
        <a href = "#${newsData[i].id}">
          ${newsData[i].title}(🖱️${newsData[i].comments_count})
        </a>
      </li>
    `);
  }
  newsList.push("</ul>");
  
  // 뉴스 목록을 container에 삽입하여 화면에 표시
  container.innerHTML = newsList.join("");
}; // end of getNewsData()

// URL 해시에서 선택된 뉴스 아이템의 ID를 추출
// 해당 ID를 사용하여 뉴스 상세 내용을 가져와 화면에 표시
newsDetail = () => {
  // URL 해시에서 뉴스 ID 추출
  // 눌렀을 때 콘솔에서 #38092612
  const id = location.hash.substring(1);
  
  // 뉴스 상세 내용 데이터 가져오기
  const ncontent = getData(CONTENT_URL.replace("@id", id));
  
  // 뉴스 상세 내용을 화면에 표시
  container.innerHTML = `
  <h1>${ncontent.title}</h1>
  <div><a href="#">목록으로</a></div>
`;
}; // end of newsDetail

// router 함수 정의
router = () => {
  // 실제로 목록을 누르면 http://localhost:57327/#을 갖는데
  // location.hash에 #만 있으면 빈값을 반환함
  const routerPath = location.hash;

  // URL 해시가 비어 있으면 뉴스 목록 표시
  if (routerPath === "") {
    // == 두 개면 값만 비교하는데 === 3개면 타입까지도 비교
    getNewsData(); // 글 목록 보기
  } else {
    // URL 해시가 있는 경우 뉴스 상세 내용 표시
    newsDetail(); // 글 내용 보기
  }
};

// 이벤트 리스너 등록 및 초기 라우팅 실행
// 해시 변화 이벤트(hashchange)가 발생할 때마다 router 함수를 호출
// 현재 URL 해시에 따라 올바른 화면을 표시
window.addEventListener("hashchange", router);
router();

라우터 관점에서는 글 내용에 대한 해시인지 글 목록을 보는 해시인지에 대한
구분이 필요할 것이다
현재는 구분할 수 없는 상태였고, 뭔가를 구분하는 구조를 만들어야 하니까
이런 방식으로 해결해 본다
최초 실행하면 화면에 아무것도 보이지 않았다
getNewsData()를 호출하는 코드가 없어서였다
그래서 라우터를 구현하여 getNewsData()를 호출할 수 있도록 해준다
그런데 해시가 바뀌면 무조건 글내용을 보는 거야 라고 구현되어 있는 것이다.
해시가 바뀌면 글 내용을 볼 수도 있고 글 목록을 볼 수도 있고 해야 하니까
그래서 이 hasgchange가 일어났을 때 동작하는 함수를 기존에 newsDetail이 아니라
라우터한테 주면 라우터가 해시가 바뀔 때마다 동작하게 되고
그럼 그 라우터 안에서 어떤 해시냐에 따라서 글목록을 보여줄 때도 있고
글 내용을 보여주기도 하는 것이다.

DEV_HTML

자동 완성 .wrap>.left+.right+.bottom

absolute position - 자유로운 엘리먼트 배치
부모 엘리먼트에 구애받지 않고 엘리먼트를 자유롭게 배치시킬 수 있다
CSS의 position 속성은 엘리먼트가 브라우저 화면에 어떻게 배치되는가를 결정함
기본값은 static 이며 relative 나 absolute, fixed 등으로 변경이 가능함
position 속성을 static이 아닌 다른 값으로 설정했을 때 함께 사용하는
포지셔닝 관련 CSS속성이 있다 (top, left, bottom, right 4가지이다)

absolute position의 특징
: 부모 엘리먼트 내부에 속박되지 않고 독립된 배치 문맥을 가짐
: 브라우저화면(viewport) 상에서 어디든지 원하는 위치에 배치 가능
심지어 부모 엘리먼트 위에 겹쳐서 배치할 수도 있음
: 단 상위 엘리먼트 중에 position 속성이 relative 인 엘리먼트가 있다면
가장 가까운 엘리먼트의 내부에서 자유 배치 가능함

a2.html

<!DOCTYPE html>
<html lang="ko">
  <!-- head태그는 화면 출력과는 관련이 없다. 
      body태그의 내용이 출력된다.
  -->
<head>
  <meta charset="UTF-8"><!-- 인코딩 타입 -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Position - absolute</title>
  <link rel="stylesheet" href="a2.css">
</head>
<body>
  <!-- .wrap>.left+right+.bottom -->
  <div class="wrap">
    <div class="left">left</div>
    <div class="right">
      <p>여기</p>
    </div>
    <div class="bottom">아래</div>
  </div>
</body>
</html>
<!-- 
  div태그는 외적으로 표시가 안 됨
  내부 텍스트노드가 차지하고 남은 곳은 모두 여백으로 채움
 -->

a2.css

.wrap {
  width: 800px;
  border: 10px solid black;
  margin: 100px auto;
}

.wrap .left {
  width: 400px;
  height: 400px;
  background-color: skyblue;
  float: left;
}

.wrap .right {
  width: 400px;
  height: 400px;
  background-color: pink;
  float: left;
}

.right p {
    width: 100px;
    height: 100px;
    background-color: lightgray;
    position: absolute;
    bottom: 20px;
    right: 20px;

}
.bottom {
  width: 800px;
  height: 100px;
  background-color: purple;
  /* clear: both; */
}

.wrap::after {
  content: "hello";
  display: block;
  clear: both;
} 

회색박스는 왜 튀어나와 있을까?

absolute는 무조건 기준점이 부모 태그가 아니라
부모자기 부모 요소중에 position이 있는 요소 중
가장 가까운 요소가 기준점이 된다
그래서 p태그 부모인 right에 position이 없으니까
위에 wrap를 보는데 여기도 position이 없으니까
부모인 body에 위치가 된다

relative는 자기 영역을 유지하면서 하는데
absolute는 자기 영역을 무시하고 뜯어져 나온다

*회색 박스를 안으로 옮겨보자
정답:

.wrap .right {
  width: 400px;
  height: 400px;
  background-color: pink;
  float: left;
  position: relative;
}

a3.html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="a3.css">
</head>
<body>
  <!-- .parent>.child+.child.abs -->
  <div class="parent">
    <div class="child">123</div>
    <div class="child abs">456</div>
  </div>
</body>
</html>

a3.css

.parent {
  border: 5px solid blue;
  color: blue;
  background-color: lightskyblue;
  padding: 1rem;
  position: relative;
}

.child {
  border: 2px dotted hotpink;
  color: black;
  background-color: wheat;
  padding: 1rem;
}

/* 띄어쓰기일 때는 상위선택자를 나타내고 붙여쓰면 현재선택자 */ 
.child.abs {
  position: absolute;
  top: 0px;
  left: 10px;
}
  • Em, rem px 차이

zindex

zindex-index.html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>z-index 실습</title>
  <link rel="stylesheet" href="index.css">
  <script src="https://kit.fontawesome.com/55780a7e38.js" crossorigin="anonymous"></script>
</head>
<body>
<!-- 메뉴바 구성하기(Navbar) -->
  <header>
    <h1>CodingGYM</h1>
    <!-- ul.gnb>li*5 -->
    <ul class="gnb">
      <li>Company</li>
      <li>gallery</li>
      <li>Community</li>
      <li>News</li>
      <li>Location</li>
    </ul>
    <ul class="sns">
      <li>
        <a href="#"><i class="fas fas fa-search"></i></a>
      </li>
      <li>
        <a href="#"><i class="far fa-envelope"></i></a>
      </li>
      <li>
        <a href="#"><i class="fab fa-facebook-square"></i></a>
      </li>
    </ul>
  </header>
<!-- 콘텐츠 구성(div) -->
  <div>
    <video src="./forest.mp4" autoplay muted loop></video>
  </div>
<!-- 바닥글 구성(footer) -->
  <footer>
    2023 CodingGYM &copy; ALL RIGHTS RESERVED
  </footer>
</body>
</html>

zindex-index.css

* {
  box-sizing: border-box; /* 1 여백을 포함하는 사이즈로 계산함 */
  border: 1px solid red; /* 2 */
}

ul {
  list-style: none; /* 3 구슬 없앰 - li가구슬 - 캡쳐링 */
}

header { /* 클래스 선택자가 아니라 태그 이름 */
  width: 100%; /* 4 반응형 웹 */
  display: flex; /* 5 ul>li로우가 늘어남 좌우 펼침 */
  justify-content: space-between; /* 6 flex 사용가능함 */
  align-items: center; /* 7 위아래 중앙 배치하기 위해 */
  top: 0px; /* 8 왼쪽 상단 모서리에 바짝 붙임 */
  left: 0px; /* 9 */
  color: white; /* 10 글씨색상 */
  z-index: 2; /* 숫자가 클수록 위에 얹혀진다 */
}

header h1 { /* 메뉴바의 사이트명 */
  font: bold 24px; /* 11 굵은 글씨 24px */
  color: white; /* 12 글자색 */
  z-index: 2; /* 13 영상 위에 보이게 */
}

header .gnb li { /* 메뉴바 li 구성-글자사이 간격 */
  /* border-box: 80+20 */
  margin: 0px 20px;/* 14 메뉴글자가 답답하니까 외부여백 */
}

.gnb {/* 글자 메뉴바 */
  display: flex; /* 15 */
  z-index: 2; /* 16 영상 위에 덮어쓴다 */
}

.sns {/* 소셜 메뉴바 */
  display: flex; /* 17 소셜아이콘 좌우로 펼치기 */
  z-index: 2; /* 18 영상 위에 덮어쓴다 */
}

header .sns li { /* header>sns>li태그 띄어쓰기 할 것 */
  margin: 0px 10px; /* 19 글자가 없어서 10만 여백 줌 */
}

header .sns li a {
  font-size: 16px; /* 20 이미지가 너무 작아서 키움 */
  color: white; /* 21 */
}

div { /* video 태그를 감싸고 있는 랩퍼태그 */
  /* 비디오 컨텐츠 */
  width: 100%; /* 22 영상이 보여지는 크기(꽉 채움) */
  height: 100vh; /* 23 보여지는 화면의 높이 */
  position: absolute; /* 24 위에 header 태그로 비디오영역이 밀림 */
  top: 0px; /* 25 y축 좌표를 모서리에 바짝 */
  left: 0px; /* 26 x축 좌표 왼쪽 모서리에 바짝 */
  overflow: hidden; /* 27 스크롤바 숨김 */
  z-index: 1; /* 28 영상이 바닥에 딸림. 2보다 작은 숫자를 줌 */
}

div video { /* div>video 반드시 띄어쓰기 */
  width: 100%; /* 29 영상이 화면에 비율을 맞춰서 꽉 채움 */
  height: 100%; /* 30 위와 동일 */
  object-fit: cover; /* 31 영상이 비디오에 꽉 차게 해줌 */
}

footer {
  width: 100%;
  position: absolute;
  bottom: 0px;
  left: 0px;
  color: white;
  z-index: 2;
  text-align: center;
  font: 0.9em/1 "arial";
  letter-spacing: 3px;
}

0개의 댓글