[JavaScript] 3-7. JavaScript DOM API

JavaScript DOM API


API 란?

Application Programming Interface 의 약자로 어플리케이션을 프로그래밍할 수 있는 접점을 의미합니다.
API 는 응용 프로그램에서 사용할 수 있도록 운영체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스로 애플리케이션을
프로그래밍 하기위해 미리만들어 놓은 기능의 집합으로 자주 사용되는 기능을 미리 만들어놨다가 필요할때 가져다 쓸수있도록
하기에 프로그래밍 작업시 효율성과 편의성을 제공합니다.



DOM 이란?

Document Object Model(문서 객체 모델)은 HTML, XML 문서의 프로그래밍 interface 입니다.
DOM 은 문서의 구조화된 표현을 제공하여, 프로그래밍 언어가 DOM 구조에 접근할 수 있는 방법을 제공하며,
그들이 문서 구조, 스타일, 내용 등을 변경할 수 있게 돕습니다.
DOM 은 구조화된 nodes 와 property 와 method 를 갖고 있는 objects 로 문서를 표현합니다.
이들은 웹 페이지를 스크립트 혹은 프로그래밍 언어들에서 사용될 수 있게 연결시켜주는 역할을 담당합니다.



DOM Tree

트리는 여러 데이터가 계층 구조 안에서 서로 연결된 형태를 나타날 때 사용됩니다.

트리는 노드로 구성되어 있는데 노드란?

다음은 노드의 종류입니다.

다음은 아주 간단한 형태의 Node를 구현한 예입니다.

class Node {
  constructor(content, children = []) {
    this.content = content;
    this.children = children;
  }
}

const tree = new Node("hello", [
  new Node("world"),
  new Node("and"),
  new Node("fun", [new Node("javascript!")])
]);

function traverse(node) {
  console.log(node.content);
  for (let child of node.children) {
    traverse(child);
  }
}

traverse(tree);
// hello world and fun javascript!

트리는 계층 구조를 나타내기 위해, 또한 계층 구조를 통해 알고리즘의 효율을 높이고자 할 때 널리 사용됩니다.



Event Reference

대표적인 이벤트들을 소개합니다.
https://developer.mozilla.org/en-US/docs/Web/Events



Web API

자바스크립트로 웹용 코드를 작성할 때 사용할 수 있는 API 가 많이 있습니다. 다음 사이트는 웹 앱이나 사이트를 개발할 때 사용할 수 있는 모든 인터페이스(즉, 객체유형) 목록이 있습니다. https://developer.mozilla.org/en-US/docs/Web/API



엘리먼트 선택하기

요소 노드(Element Node)를 선택하는 방법은 다음과 같습니다.

// 사용방법
document.querySelector("CSS 선택자");
// 사용방법
document.querySelectorAll("CSS 선택자");
// 사용방법
body.querySelector("CSS 선택자");
<article>
  <div id="div-01">Here is div-01
    <div id="div-02">Here is div-02
      <div id="div-03">Here is div-03</div>
    </div>
  </div>
</article>
// 사용 방법

let el = document.querySelector("#div-03");

let r1 = el.closest("#div-02");
// id가 div-02인 요소를 반환합니다. (div-03이 자식이기에 CSS를 적용하면 같이 적용됩니다.)

let r2 = el.closest("div div");
// div의 div인 가장 가까운 조상을 반환합니다. div-03이 반환됩니다.

let r3 = el.closest("article > div");
// div이면서 부모 article이 있는 가장 가까운 상위 항목을 반환합니다. div-01이 반환됩니다.

let r4 = el.closest(":not(div)");
// div가 아닌 가장 가까운 조상을 반환합니다. 여기에 outmost article이 있습니다.
<ul id="birds">
  <li>Orange-winged parrot</li>
  <li class="endangered">Philippine eagle</li>
  <li>Great white pelican</li>
</ul>
let birds = document.querySelectorAll("li"); // document의 모든 li요소를 선택하여 변수 birds에 값으로 대입

for (let i = 0; i < birds.length; i++) {
  // i가 0일때부터 birds.length(3)까지 i++
  if (birds[i].matches(".endangered")) {
    // 만약 birds[i]가 endangered라는 클래스 네임을 갖고 있을 경우
    console.log("The " + birds[i].textContent + " is endangered!"); // 해당 구문 출력
  }
}

// 결과 :  The Philippine eagle is endangered!



엘리먼트 내용 조작하기

const text = element.textContent;
element.textContent = "this is some sample text";
const content = element.innerHTML;
element.innerHTML = htmlString;

여기서 잠깐!!

HTML 콘텐츠를 조작(Manipulation)하기 위해 위와 같은 프로퍼티 또는 메소드를 사용할 수 있습니다.
하지만 마크업이 포함된 콘텐츠를 추가하는 행위는 크로스 스크립팅 공격(XSS: Cross-Site Scripting Attacks)에 취약하므로 주의가 필요합니다.
가급적 텍스트 노드를 다룰때는 마크업이 무시되는 textContent 를 사용하는 것이 좋습니다.

그렇다면 크로스 스크립팅 공격이란?

// 크로스 스크립팅 공격 사례

// 스크립트 태그를 추가하여 자바스크립트가 실행되도록 합니다.
// HTML5에서 innerHTML로 삽입된 <script> 코드는 실행되지 않습니다.
// 크롬, 파이어폭스 등의 브라우저나 최신 브라우저 환경에서는 작동하지 않을 수도 있습니다.
elem.innerHTML = '<script>alert("XSS!")</script>';

// 에러 이벤트를 발생시켜 스크립트가 실행되도록 합니다.
// 크롬에서도 실행됩니다!
elem.innerHTML = '<img src="#" onerror="alert(\'XSS\')">';

// 자바스크립트 링크 공격
// 링크 태그로 자바스크립트를 실행시킵니다.
elem.innerHTML = '<a href="javascript:alert("XSS")">XSS</a>';

SQL injection(데이터베이스 조회를 통한 공격)과 함께 웹 상에서 가장 기초적인 취약점 공격 방법의 일종으로,
악의적인 사용자가 공격하려는 사이트에 스크립트를 넣는 기법을 말합니다.
공격에 성공하면 사이트에 접속한 사용자는 삽입된 코드를 실행하게 되며,
보통 의도치 않은 행동을 수행시키거나 쿠키나 세션 토큰등의 민감한 정보를 탈취합니다.



엘리먼트 어트리뷰트 조작하기

// 작성 방법
let result = element.hasAttribute("CSS 선택자");

// 사용 방법
let foo = document.querySelector("#foo");
if (foo.hasAttribute("bar")) {
  // 부울 값을 반환하므로 조건문으로 활용할 수 있습니다.
  // do something
}
// 작성 방법
let attribute = element.getAttribute("어트리뷰트 이름");

// 사용 방법
let div1 = document.querySelector("#div1");
let align = div1.getAttribute("align");

alert(align); // shows the value of align for the element with id="div1"
<button>Hello World</button>
const b = document.querySelector("button");

b.setAttribute("name", "helloButton");
b.setAttribute("disabled", "");
// <div id="div1" align="left" width="200px">
document.querySelector("#div1").removeAttribute("align");
// align 속성을 제거시켰으므로 다음과 같이 출력됩니다.
// <div id="div1" width="200px">



엘리먼트 클래스 조작하기

// div는 class = 'foo bar'인 요소에 대한 객체 참조입니다.
div.classList.remove("foo");
div.classList.add("anotherclass");

// visible이 설정되어있으면 제거하고, 그렇지 않으면 추가합니다.
div.classList.toggle("visible");

// i가 10보다 작으면 visible을 추가/제거합니다.
div.classList.toggle("visible", i < 10);

// foo라는 값을 포함하고 있으면 경고창을 켭니다.
alert(div.classList.contains("foo"));

// 여러 클래스를 더하거나 제거할수 있습니다.
div.classList.add("foo", "bar", "baz");
div.classList.remove("foo", "bar", "baz");

// 스프레드 문법을 이용하여 추가 제거도 가능합ㄴ디ㅏ.
let cls = ["foo", "bar"];
div.classList.add(...cls);
div.classList.remove(...cls);

// foo를 bar로 대체합니다.
div.classList.replace("foo", "bar");



인라인 스타일 조작하기

// 여러 style 설정을 한줄로 할 수 있습니다.
elt.style.cssText = "color: blue; border: 1px solid black";
// 위처럼 뿐만 아니라 아래처럼도 style 적용을 할 수 있습ㄴ디ㅏ.
elt.setAttribute("style", "color:red; border: 1px solid blue;");

elt.style.color = "blue"; // 다른 인라인 스타일 값을 변경하지 않고 특정 스타일을 설정합니다.



이벤트 리스너

// 작성 방법
target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);
target.addEventListener(type, listener[, useCapture, wantsUntrusted ]);
// Gecko/Mozilla only (크롬, 파이어폭스, 사파리)
// ie의 경우 addEventListenter를 지원하지 않으며 attachevent로 이벤트를 추가해야 합니다.

// 사용 방법
function modifyText() {
  let t2 = document.querySelector("#t2");
  if (t2.firstChild.nodeValue == "three") {
    t2.firstChild.nodeValue = "two";
  } else {
    t2.firstChild.nodeValue = "three";
  }
}

// add event listener to table
let el = document.querySelector("#outside");
el.addEventListener("click", modifyText, false);
// 작성 방법
target.removeEventListener(type, listener[, options]);
target.removeEventListener(type, listener[, useCapture]);

// 사용 방법
element.removeEventListener("mousedown", handleMouseDown, false);     // Fails
element.removeEventListener("mousedown", handleMouseDown, true);      // Succeeds



DOM 노드 생성하기

// 작성 방법
let element = document.createElement(tagName[, options]);

// 사용 방법
document.body.onload = addElement;

function addElement () {
  // div 요소를 만들어 newDiv에 값으로 대입합니다.
  let newDiv = document.createElement("div");
  // Hi there and greetings!이라는 텍스트노드를 만들어 newContent에 값으로 대입합니다.
  let newContent = document.createTextNode("Hi there and greetings!");
  // newDiv에 텍스트 노드를 추가합니다.
  newDiv.appendChild(newContent);

  // 새로 생성 된 요소와 그 내용을 DOM에 추가합니다.
  let currentDiv = document.querySelectro("#div1");
  document.body.insertBefore(newDiv, currentDiv);
}
// 작성 방법
let dupNode = node.cloneNode([deep]);
// 노드의 아이도 복제 할 필요가있는 경우는 true, 지정된 노드만을 복제하는 경우는 false 지정해줍니다.

// 사용 방법
let p = document.querySelector("#para1");
let p_prime = p.cloneNode(true);



DOM 트리 조작하기

// 작성 방법
let aChild = element.appendChild(aChild);

// 사용 방법
let p = document.createElement("p");
document.body.appendChild(p);
// 작성 방법
let insertedNode = parentNode.insertBefore(newNode, referenceNode);

// 사용 방법
<div id="parentElement">
  <span id="childElement">foo bar</span>
</div>;

// span 요소를 만들어 newNode에 값으로 대입합니다.
const newNode = document.createElement("span");

// 부모 노드에 대한 참조를 얻습니다.
const parentDiv = document.getElementById("childElement").parentNode;

// 테스트 케이스 시작 [1] : Exist a childElement -> 모두 올바르게 작동합니다.
const sp2 = document.getElementById("childElement");
parentDiv.insertBefore(newNode, sp2);
// 테스트 케이스 [ 1 ] 종료

// 테스트 케이스 시작 [2] : childElement는 유형이 정의되지 않았습니다.
const sp2 = undefined; // ID가 'childElement'인 노드가 존재하지 않습니다.
parentDiv.insertBefore(newNode, sp2); // 입력 노드에 대한 암시적 동적 캐스트
// 테스트 케이스 [ 2 ] 종료

// 테스트 케이스 시작 [3] : childElement는 'undefined'(string) 유형입니다.
const sp2 = "undefined"; // id가 'childElement'인 노드가 존재하지 않습니다.
parentDiv.insertBefore(newNode, sp2); // '유형 오류 : 잘못된 인수' 생성
// 테스트 케이스 [ 3 ] 종료
// 작성 방법
replacedNode = parentNode.replaceChild(newChild, oldChild);

// 사용 방법

// <div>
//  <span id="childSpan">foo bar</span>
// </div>
const sp1 = document.createElement("span");
sp1.id = "newSpan";

const sp1_content = document.createTextNode("new replacement span element.");
sp1.appendChild(sp1_content);

const sp2 = document.getElementById("childSpan");
const parentDiv = sp2.parentNode;

// sp2를 sp1로 대체 합니다.
parentDiv.replaceChild(sp1, sp2);

// result:
// <div>
//   <span id="newSpan">new replacement span element.</span>
// </div>
// 작성 방법
const oldChild = node.removeChild(child);
OR;
node.removeChild(child);

// 사용 방법
const d = document.getElementById("top");
const d_nested = document.getElementById("nested");
const throwawayNode = d.removeChild(d_nested);



dataset

// 사용 및 작성 방법
<div data-index="hello" data-name="moong2" name="hwan"></div>



노드 간 관계



엘리먼트 크기 및 위치



이벤트 전파



이벤트 객체

// list 생성
let ul = document.createElement("ul");
document.body.appendChild(ul);

let li1 = document.createElement("li");
let li2 = document.createElement("li");
ul.appendChild(li1);
ul.appendChild(li2);

function hide(e) {
  // e.target은 클릭 된 요소를 참조합니다.
  // 이것은 이 컨텍스트에서 부모를 참조하는 e.currentTarget과 다릅니다.
  e.target.style.visibility = "hidden";
}

// Listener를 목록에 첨부하십시오.
// 각 li를 클릭하면 실행됩니다.
ul.addEventListener("click", hide, false);
function hide(e) {
  e.currentTarget.style.visibility = "hidden";
  console.log(e.currentTarget);
  // 이 함수가 이벤트 핸들러로 사용될 때 : this === e.currentTarget
}

let ps = document.getElementsByTagName("p");

for (let i = 0; i < ps.length; i++) {
  // 콘솔에서 클릭된 p요소를 출력합니다.
  ps[i].addEventListener("click", hide, false);
}
// body 추력
document.body.addEventListener("click", hide, false);

// 주위를 클릭하면 단락을 사라지게합니다.
// 사용 방법 - 사용할 이벤트의 메소드처럼 사용하도록 합니다.
event.stopPropagation();
function checkName(evt) {
  var charCode = evt.charCode;
  if (charCode != 0) {
    if (charCode < 97 || charCode > 122) {
      evt.preventDefault();
      displayWarning(
        "Please use lowercase letters only." +
          "\n" +
          "charCode: " +
          charCode +
          "\n"
      );
    }
  }
}



폼 이벤트

// 사용 방법
document.querySelector("select").addEventListener("change", e => {
  alert(e.target.value);
});
// 사용 방법
document.querySelector(".name").addEventListener("input", e => {
  document.querySelector(".named").textContent = e.target.value;
});
nameEl.addEventListener("focus", e => {
  console.log("focus");
});
nameEl.addEventListener("blur", e => {
  console.log("blur");
});
<!--
 과거에는 html에서 type 어트리뷰트로 submitd을 통해 전송했으나
 전송과 동시에 새로고침 혹은 주소변경되는 이슈로 최근에는 쓰지 않는다.-->
<input type="submit" value="전송">
// 대신 submit 이벤트를 사용한다.
const formEl = document.querySelector("form");
formEl.addEventListener("submit", e => {
  e.preventDefault();
  alert(e.target.elements.gender.value);
  alert(e.target.elements.name.value);
});



마우스 이벤트

document.querySelector("a").addEventListener("click", e => {
  alert("링크가 클릭되었습니다.");
  e.preventDefault();
});
const boxEl = document.querySelector(".box");
boxEl.addEventListener("mouseover", e => {
  console.log("mouseover");
});
boxEl.addEventListener("mouseout", e => {
  console.log("mouseout");
});
// 드래그 앤 드롭을 구현하고자 할 때 mousedown / mouseup을 씁니다.
const boxEl = document.querySelector(".box");
boxEl.addEventListener("mousedown", e => {
  console.log("mousedown");
});
boxEl.addEventListener("mouseup", e => {
  console.log("mouseup");
});
// 요소위에서 조금이라도 움직이면 mousemove는 발생합니다.
let count = 0;
boxEl.addEventListener("mousemove", e => {
  console.log(`mousemove : ${count++}`);
});

[mousedown, mouseup, mousemove 를 이용한 drag&drop 구현]

const boxEl = document.querySelector(".box");

let dragging = false;
let originalOffset;
let originalMousePos;

document.addEventListener("mousemove", e => {
  if (dragging) {
    const newTop = `${originalOffset.top + (e.clientY - originalMousePos.y)}px`;
    const newLeft = `${originalOffset.left +
      (e.clientX - originalMousePos.x)}px`;
    boxEl.style.top = newTop;
    boxEl.style.left = newLeft;
  }
  console.log(`x:${e.clientX}, y:${e.clientY}`);
});

boxEl.addEventListener("mousedown", e => {
  dragging = true;
  originalOffset = {
    top: boxEl.offsetTop,
    left: boxEl.offsetLeft
  };
  originalMousePos = {
    x: e.clientX,
    y: e.clientY
  };
});

boxEl.addEventListener("mouseup", e => {
  dragging = false;
});



키보드 이벤트

const inputEl = document.querySelector("input");

inputEl.addEventListener("keydown", e => {
  console.log("keydown");
});

inputEl.addEventListener("keyup", e => {
  console.log("keyup");
});



스크롤 이벤트

document.addEventListener("click", e => {
  console.log("scroll" + window.scrollY);
});



2. Today I Found Out

하루동안 연산자 심화 공부와 DOM을 공부하였는데
중요한 부분이라서 조금 더 시간을 갖고 공부를 하였습니다.
DOM트리 그림을 보면서 하나하나 이해하려고 노력하였고,
그 결과 오늘 공부한 것까지 이해를 할 수 있었습니다.
오늘 공부한 것을 정리해보면서 생각해보니
그간 독학으로 공부하면서 혼란스러웠던 부분이
상당부분 정리가 되었음을 체감할 수 있었습니다.
앞으로도 계속 열심히 해야겠습니다.



3. refer

https://github.com/fds9/fds-dom-api

https://devdocs.io/dom/node

http://poiemaweb.com/js-dom