|
|
난이도 : 초급
Brett McLaughlin, 저자 겸 편집자, O'Reilly Media Inc.
2006 년 8 월 07 일 2006 년 12 월 12 일 수정
지난 달에는 웹 페이지를 정의하는 문서 객체 모델을 소개했습니다. 이번 달에는 돔을 보다 자세히 연구합니다. 돔 트리의 부분들을 생성, 제거, 변경하는 방법을 설명하고 그 다음 단계인 웹 페이지를 업데이트 하는 방법을 설명합니다.
시리즈 소개
지난 달에는 웹 브라우저가 웹 페이지들 중 하나를 디스플레이 할 때 어떤 일이 일어나는지에 대해 설명했다. 페이지에 정의했던 HTML과 CSS가 웹 브라우저로 보내지면 이것은 텍스트에서 객체 모델로 변환된다. 코드가 단순하건 복잡하건 간에, 하나의 파일에 저장하든 또는 개별 파일들에 모든 것을 저장하든 간에 이것은 사실이다. 브라우저는 제공된 텍스트 파일 보다는 객체 모델을 사용한다. 브라우저가 사용하는 모델을 문서 객체 모델(Document Object Model)이라고 한다. 이것은 문서에 있는 엘리먼트, 애트리뷰트, 텍스트를 나타내는 객체들을 연결한다. HTML과 CSS에 있는 모든 스타일, 값, 심지어 거의 모든 공간들은 객체 모델로 통합된다. 웹 페이지에 대한 특정 모델은 그 페이지의DOM 트리라고 한다.
DOM 트리가 무엇인지를 이해하고 이것이 어떻게 HTML과 CSS를 나타내는지를 아는 것이 웹 페이지를 제어하는 첫 번째 단계이다. 그런 다음에는 특정 웹 페이지에 DOM 트리를 사용하여 작동시키는 방법을 배워야 한다. 예를 들어, 한 엘리먼트를 DOM 트리에 추가하면 그 엘리먼트는 페이지 리로딩 없이 사용자의 웹 브라우저에 즉시 나타난다. DOM 트리에서 몇몇 텍스트를 제거하면 그 텍스트는 사용자의 스크린에서 사라진다. 여러분은 DOM을 통해서 사용자 인터페이스를 변경할 수 있고 인터랙팅 할 수 있다. 이것은 실로 엄청난 프로그래밍의 힘과 유연성을 제공해 준다. 일단 DOM 트리로 작업하는 방법을 배우면 풍부하고 동적인 인터랙티브 웹 사이트를 마스터하는 단계로 넘어갈 수 있다.
지난 달 "웹 응답에 DOM 활용하기"를 참조하라. 아직 읽어 보지 않았다면 이 글을 읽기 전에 일어보기 바란다.
|
약어 문제
Document Object Model은 그 동안 Document Node Model로 불려졌다. 물론 대부분의 사람들은 노드(node)라는 단어가 무엇을 의미하는지 모르고, "DNM"은 "DOM"만큼 발음하기 쉬운 것도 아니기 때문에 W3C에서도 DOM이라는 용어를 채택했다. | |
크로스 브라우저, 크로스 언어
Document Object Model은 W3C 표준이다. (참고자료) 따라서 모든 현대의 웹 브라우저는 DOM을 지원하고 있다. 브라우저 간 차이는 있겠지만 핵심 DOM 기능을 사용한다면-그리고 특별한 케이스와 예외에 주의를 기울이면- 여러분의 DOM 코드는 어떤 브라우저 상에서도 같은 방식으로 작동할 것이다. Opera의 웹 페이지를 수정하기 위해 여러분이 작성한 코드는 Apple의 Safari®, Firefox®, Microsoft® Internet Explorer®, Mozilla® 상에서도 실행될 것이다.
DOM은 크로스 언어(cross-language) 스팩이기도 하다. 다시 말해서 대부분의 프로그래밍 언어에서 이것을 사용할 수 있다. W3C는 DOM을 위한 여러 언어 바인딩을 정의하고 있다. 언어 바인딩은 단순한 API로서 특정 언어에 DOM을 사용할 수 있도록 해준다. 예를 들어, C, Java, JavaScript용으로 정의가 잘 된 DOM 언어 바인딩을 찾아볼 수 있다. 따라서 여러분은 어떤 언어에서도 DOM을 사용할 수 있다. 언어 바인딩 역시 여러 다른 언어에도 사용할 수 있다. 비록 이 언어들이 W3C가 아닌 삼자에 의해 정의되더라도 말이다.
이 시리즈에서 나는 JavaScript 바인딩에 초점을 맞출 것이다. 대부분의 비동기식 애플리케이션 개발이 JavaScript 코드를 작성하여 웹 브라우저에서 실행하기 때문이다. JavaScript와 DOM을 사용하여 사용자 인터페이스를 수정할 수 있고 사용자 이벤트와 인풋에 대응할 수 있다. 이 모두가 완전히 표준화된 JavaScript를 사용한다.
다른 언어로 된 DOM 언어 바인딩도 검토해보기 바란다. 예를 들어, 자바 언어 바인딩을 사용하여 HTML 뿐만 아니라 XML로도 작업할 수 있다. 따라서 여기에서 배운 레슨을 HTML 외에도 적용할 수 있고 클라이언트 측 JavaScript 말고도 여러 환경에 적용할 수 있다.
개념상의 노드
노드는 DOM에서 가장 기본적인 객체 유형이다. 사실, 이 글에서 밝혀지겠지만 DOM에 의해 정의된 거의 모든 객체는 노드 객체를 확장한다. 하지만 의미론으로 들어가기 전에 노드의 개념을 이해해야 한다. 그렇게 하면 실제 속성과 노드의 방식을 배우는 것은 식은 죽 먹기다.
DOM 트리에서 여러분이 만나게 되는 거의 모든 것이 노드이다. 가장 기본적인 레벨에 있고, DOM 트리에 있는 노드이다. 모든 애트리뷰트는 하나의 노드이다. 모든 텍스트 조각도 하나의 노드이다. 심지어 코멘트, (카피라이트 상징을 나타내는 © 같은) 특별 문자도 모두 노드이다. 이러한 개별 유형들의 특성을 설명하기 전에, 여러분은 노드가 무엇인지를 이해해야 한다.
노드란..
간단히 말해서, 노드는 DOM 트리에 있는 그저 단순한 하나의 존재이다. "존재(thing)"라고 한 것은 다분히 의도적이다. 예를 들어, HTML에 있는 엘리먼트 img 와 HTML의 텍스트 조각인 "Scroll down for more details"는 많은 공통점이 있다. 하지만 그러한 개별 유형의 기능에 대해 생각하기 때문에 이들이 어떻게 다른지에 초점을 맞추겠다.
DOM 트리에 있는 각 엘리먼트와 텍스트 조각이 부모를 갖고 있다고 생각해 보자. 부모는 또 다른 엘리먼트의 자식일 수도 있고(img 가 p 엘리먼트 안에 중첩될 때) 또는 DOM 트리의 가장 위에 있는 엘리먼트일 수 있다. (이것은 각 문서에 대해 단 한번의 특별한 경우이고 이곳에서 html 엘리먼트를 사용한다.) 또한 엘리먼트와 텍스트 모두 type을 갖고 있다고 생각해 보라. 한 엘리먼트의 type은 분명히 하나의 엘리먼트이다. 텍스트의 type은 텍스트이다. 각 노드는 또한 정의가 잘된 구조를 갖고 있다. 이 밑에 자식 엘리먼트 같은 하나의 노드(또는 노드들)을 갖는가? 이것이 자매 노드(sibling node) (엘리먼트나 텍스트 옆에 있는 노드)를 갖는가? 각 노드에는 어떤 문서에 속하는가?
다분히 추상적으로 들린다. 사실 엘리먼트의 유형은 음… 엘리먼트이다 라고 말하는 것은 어리석어 보인다. 하지만 일반 객체 유형으로서 노드를 갖는다는 것의 가치는 추상적으로 이해해야 한다.
공통 노드 유형
여러분의 DOM 코드에서 그 어떤 것 보다 많이 수행하는 일은 도움 트리 안을 검색하는 것이다. 예를 들어, "id" 애트리뷰트 별로 form 을 배치하고 그 form 안에 중첩된 엘리먼트와 텍스트로 작업하기 시작한다. 텍스트 명령어, 인풋 필드용 레이블, 실제 input 엘리먼트, img 엘리먼트와 링크(a 엘리먼트) 같은 HTML 엘리먼트가 있을 것이다. 엘리먼트와 텍스트가 완전히 다른 유형유형이라면 여러분은 완전히 다른 코드를 작성하여 한 유형에서 다른 유형으로 옮겨가야 한다.
공통 노드 유형을 사용한다면 상황은 달라진다. 이 경우 노드에서 노드로 간단히 움직일 수 있고 엘리먼트나 텍스트의 특정의 것을 수행하고 싶을 때에만 노드의 유형을 생각하면 된다. DOM 트리 주위로 이동할 때 같은 연산을 사용하여 엘리먼트의 부모 또는 자식으로 이동한다. 엘리먼트의 애트리뷰트 같은 특정 노드 유형에서 구체적인 작업이 필요하다면 엘리먼트나 텍스트 같은 하나의 노드 유형에 대해서만 작업하면 된다. DOM 트리에 있는 각 객체를 하나의 노드로 생각하면 훨씬 간단하게 작업할 수 있다. 이제부터는 DOM Node 구조가 정확히 무엇을 제공하는지를 설명하겠다. 속성과 메소드부터 시작한다.
노드의 속성
DOM 노드와 작업할 때 여러 속성과 메소드를 사용한다면 다음 사항들을 먼저 생각해야 한다. 다음은 DOM 노드의 핵심 속성이다.
nodeName 은 노드의 이름을 나타낸다. (아래 참조)
nodeValue :는 노드의 "값"을 제공한다. (아래 참조)
parentNode 는 노드의 부모를 리턴한다. 모든 엘리먼트, 애트리뷰트, 텍스트는 부모 노드를 갖고 있다는 것을 기억하라.
childNodes 는 노드의 자식들의 리스트이다. HTML로 작업할 때 엘리먼트를 다룰 경우 이 리스트는 유용하다. 텍스트 노드와 애트리뷰트 노드는 어떤 자식도 갖지 않는다.
firstChild 는 childNodes 리스트에 있는 첫 번째 노드로 가는 지름길이다.
lastChild 도 또 다른 지름길이다. childNodes 리스트에 있는 마지막 노드로 가는 지름길이다.
previousSibling 은 현재 노드 앞에 있는 노드를 리턴한다. 다시 말해서, 현재 것 보다 앞에 있는 노드를 리턴한다. 현재 노드의 부모의 childNodes 리스트에 있는 것 보다 선행하는 노드를 리턴한다. (헷갈린다면 마지막 문장을 다시 한번 더 읽어라.)
nextSibling 은 previousSibling 속성과 비슷하다. 부모의 childNodes 리스트에 있는 다음 노드로 돌린다.
attributes 는 엘리먼트 노드에서 유일하게 유용한 것이다. 엘리먼트의 애트리뷰트 리스트를 리턴한다.
몇몇 다른 속성들은 보다 근본적인 XML 문서로 적용되고 HTML 기반의 웹 페이지로 작업할 때 많이 사용되지 않는다.
특별한 속성
위에서 정의된 대부분의 속성들이 다분히 설명적이다. nodeName 과 nodeValue 속성을 빼면 말이다. 이러한 속성들을 설명하지 않고 대신 기이한 질문 두 가지를 생각해 보자. 텍스트 노드용 nodeName 은 무엇인가? 혹은 엘리먼트용 nodeValue 는 무엇이 될까?
이러한 질문들이 당황스럽다면 이러한 속성들에 내재해 있는 혼돈을 이미 이해하고 있는 것이다. nodeName 과 nodeValue 는 실제로 모든 노드 유형에 적용되는 것은 아니다. (한 노드 상의 다른 속성들의 경우도 마찬가지다.) 이 속성들은 null 값을 리턴할 수 있다. (JavaScript에서는 "undefined"로 나타난다.) 예를 들어, 텍스트 노드용 nodeName 속성은 무효이다. (또는 어떤 브라우저에서는 "undefined"이다. 텍스트 노드는 이름이 없기 때문이다. nodeValue 는 노드의 텍스트를 리턴한다.)
비슷하게, 엘리먼트는 nodeName --엘리먼트의 이름--을 갖고 있지만 엘리먼트의 nodeValue 속성의 값은 언제나 무효이다. 애트리뷰트는 nodeName 과 nodeValue , 이 두 가지 속성에 대한 값을 갖는다. 이 개별 유형들을 다음 섹션에서는 자세히 설명하도록 하겠다.
Listing 1은 실행 중인 여러 노드 속성들을 보여주고 있다. Listing 1. DOM에서 노드 속성 사용하기
// These first two lines get the DOM tree for the current Web page,
// and then the <html> element for that DOM tree
var myDocument = document;
var htmlElement = myDocument.documentElement;
// What's the name of the <html> element? "html"
alert("The root element of the page is " + htmlElement.nodeName);
// Look for the <head> element
var headElement = htmlElement.getElementsByTagName("head")[0];
if (headElement != null) {
alert("We found the head element, named " + headElement.nodeName);
// Print out the title of the page
var titleElement = headElement.getElementsByTagName("title")[0];
if (titleElement != null) {
// The text will be the first child node of the <title> element
var titleText = titleElement.firstChild;
// We can get the text of the text node with nodeValue
alert("The page title is '" + titleText.nodeValue + "'");
}
// After <head> is <body>
var bodyElement = headElement.nextSibling;
while (bodyElement.nodeName.toLowerCase() != "body") {
bodyElement = bodyElement.nextSibling;
}
// We found the <body> element...
// We'll do more when we know some methods on the nodes.
}
|
|
|
노드의 방식
다음은 모든 노드에 사용할 수 있는 메소드를 소개하겠다. (노드 속성을 설명할 때 처럼, 대부분의 HTML DOM 연산에는 적용되지 않는 메소드는 제외했다.)
insertBefore(newChild, referenceNode) 는 newChild 노드를 referenceNode 앞에 삽입한다. newChild 의 미래의 부모에 대해 이것을 호출할 것이다.
replaceChild(newChild, oldChild) 는 oldChild 노드를 newChild 노드로 교체한다.
removeChild(oldChild) 는 함수가 실행되고 있는 노드에서 oldChild 노드를 제거한다.
appendChild(newChild) 는 newChild 노드를 이 함수가 실행되고 있는 노드에 추가한다. newChild 는 목표 노드의 자식들의 끝에 추가된다.
hasChildNodes() 는 호출된 노드가 자식을 갖고 있을 경우 true를 , 그렇지 않을 경우 false를 리턴한다.
hasAttributes() 는 호출된 노드가 애트리뷰트를 갖고 있을 경우 true를, 애트리뷰트가 없을 경우 false를 리턴한다.
대부분의 경우, 이 모든 메소드들이 노드의 자식들을 다루고 있다. 이것이 바로 그들의 주요 목적이다. 텍스트 노드나 엘리먼트의 이름 값을 파악하기 위해 메소드를 많이 호출할 필요가 없다. 단순히 노드의 속성을 사용하면 된다. Listing 2는 Listing 1을 기반으로 위 여러 메소드를 사용하여 코드를 구현한 것이다. Listing 2. DOM에서 노드 메소드 사용하기
// These first two lines get the DOM tree for the current Web page,
// and then the <html> element for that DOM tree
var myDocument = document;
var htmlElement = myDocument.documentElement;
// What's the name of the <html> element? "html"
alert("The root element of the page is " + htmlElement.nodeName);
// Look for the <head> element
var headElement = htmlElement.getElementsByTagName("head")[0];
if (headElement != null) {
alert("We found the head element, named " + headElement.nodeName);
// Print out the title of the page
var titleElement = headElement.getElementsByTagName("title")[0];
if (titleElement != null) {
// The text will be the first child node of the <title> element
var titleText = titleElement.firstChild;
// We can get the text of the text node with nodeValue
alert("The page title is '" + titleText.nodeValue + "'");
}
// After <head> is <body>
var bodyElement = headElement.nextSibling;
while (bodyElement.nodeName.toLowerCase() != "body") {
bodyElement = bodyElement.nextSibling;
}
// We found the <body> element...
// Remove all the top-level <img> elements in the body
if (bodyElement.hasChildNodes()) {
for (i=0; i<bodyElement.childNodes.length; i++) {
var currentNode = bodyElement.childNodes[i];
if (currentNode.nodeName.toLowerCase() == "img") {
bodyElement.removeChild(currentNode);
}
}
}
}
|
Test me!
지금까지 Listing 1과 2의 두 가지 예제를 봤다. 하지만 이것은 DOM 트리를 조작할 때 무엇이 가능한지에 대한 아이디어에 불과하다. 지금까지의 코드를 실행해 보려면 Listing 3을 HTML 파일에 놓고 이를 저장하여 웹 브라우저로 로딩한다. Listing 3. DOM을 사용하는 HTML 파일과 JavaScript 코드
<html>
<head>
<title>JavaScript and the DOM</title>
<script language="JavaScript">
function test() {
// These first two lines get the DOM tree for the current Web page,
// and then the <html> element for that DOM tree
var myDocument = document;
var htmlElement = myDocument.documentElement;
// What's the name of the <html> element? "html"
alert("The root element of the page is " + htmlElement.nodeName);
// Look for the <head> element
var headElement = htmlElement.getElementsByTagName("head")[0];
if (headElement != null) {
alert("We found the head element, named " + headElement.nodeName);
// Print out the title of the page
var titleElement = headElement.getElementsByTagName("title")[0];
if (titleElement != null) {
// The text will be the first child node of the <title> element
var titleText = titleElement.firstChild;
// We can get the text of the text node with nodeValue
alert("The page title is '" + titleText.nodeValue + "'");
}
// After <head> is <body>
var bodyElement = headElement.nextSibling;
while (bodyElement.nodeName.toLowerCase() != "body") {
bodyElement = bodyElement.nextSibling;
}
// We found the <body> element...
// Remove all the top-level <img> elements in the body
if (bodyElement.hasChildNodes()) {
for (i=0; i<bodyElement.childNodes.length; i++) {
var currentNode = bodyElement.childNodes[i];
if (currentNode.nodeName.toLowerCase() == "img") {
bodyElement.removeChild(currentNode);
}
}
}
}
}
</script>
</head>
<body>
<p>JavaScript and DOM are a perfect match.
You can read more in <i>Head Rush Ajax</i>.</p>
<img src="http://www.headfirstlabs.com/Images/hraj_cover-150.jpg" />
<input type="button" value="Test me!" onClick="test();" />
</body>
</html>
|
이 페이지를 브라우저로 로딩하면 그림 1과 같이 된다. 그림 1. JavaScript를 실행하는 버튼이 있는 간단한 HTML 페이지
Click Test me!를 클릭하면 경고 박스가 나타난다. (그림 2) 그림 2. nodeValue를 사용하여 엘리먼트의 이름을 보여주는 경고 박스
코드 실행이 종료되면 이미지가 페이지에서 실시간으로 제거된다. (그림 3) 그림 3. JavaScript를 사용하여 실시간으로 제거되는 이미지
API 디자인 설명
각 노드에서 사용할 수 있는 속성과 메소드를 다시 한 번 보자. 이것은 객체 지향(OO) 프로그래밍에 익숙한 사람들을 위해 DOM의 핵심 포인트를 나타낸다. DOM은 객체 지향 API가 아니다. 우선, 많은 경우 여러분은 노드 객체에 메소드를 호출하기 보다는 객체의 속성들을 직접적으로 사용할 것이다. 예를 들어, getNodeName() 메소드가 없다. 다만 nodeName 속성을 직접 사용한다. 따라서 노드 객체들은(그리고 다른 DOM 객체들은) 함수가 아닌 속성들을 통해 많은 데이터를 노출한다.
두 번째로, DOM의 객체와 메소드의 네이밍은 약간 낯설다. 만약 여러분이 객체와 객체 지향 API로 작업했다면 그렇게 느낄 것이다. (특히 자바나 C++). DOM은 C, 자바, JavaScript)에서 작동하기 때문에 일부 잉여물들이 API 디자인에 만들어 진다. 예를 들어, NamedNodeMap 메소드에 있는 두 개의 다른 메소드를 보자.
getNamedItem(String name)
getNamedItemNS(Node node)
OO 프로그래머에게는 이것은 매우 이상해 보인다. 같은 목적을 갖고 있는 두 개의 메소드지만 하나는 String을 또 하나는 Node 를 취한다. 대부분의 OO API에서 두 버전에 같은 메소드 이름을 사용한다. 코드를 실행하는 가상 머신은 메소드로 전달된 객체 유형에 기반하여 어떤 메소드를 실행할 것인지를 규명한다.
문제는 JavaScript가 메소드 오버로딩(method overloading)이라는 기술을 지원하지 않는다는 점이다. 다시 말해서, JavaScript에서는 주어진 이름에 대해 하나의 메소드나 함수를 가져야 한다는 의미이다. 따라서 스트링을 취하는 getNamedItem() 메소드를 갖고 있다면 getNamedItem() 이라는 이름의 다른 메소드나 함수를 가질 수 없다. 두 번째 버전이 다른 유형의 인자를 취하더라도 말이다. (또는 완전히 다른 인자를 취해도 마찬가지다.) 그럴 경우 JavaScript는 에러를 보고하고 코드는 작동하지 않는다.
본질적으로 DOM은 메소드 오버로딩과 다른 OO 프로그래밍 기술들을 피한다. OO 프로그래밍 기술을 지원하지 않는 언어를 포함하여 API는 여러 언어들을 통해서 작동한다는 확신 때문이다. 결국 몇 가지 추가적인 메소드 이름을 배워야 한다는 것이다. 예를 들어 자바 같은 언어에서 DOM을 배울 수 있고 같은 메소드 이름과 코딩 구조가 자바스크립트 같은 DOM 구현을 가진 다른 언어들에도 작동할 것이라는 것을 알 수 있다.
프로그래머를 조심시켜라!
여러분이 API 디자인에 몰두해 있다면 궁금할 것이다. "왜 속성은 모든 노드에 대해 공통적이지 않는가?" 이것은 기술적인 이유 보다는 정치적이고 의사 결정에 관한 것이다. 간단히 말해서 정답은, "누가 알겠는가! 너무 귀찮다." 라고 밖에 말할 수 없다.
nodeName nodeName 속성은 모든 유형이 이름을 가질 수 있도록 하고 있다. 하지만 많은 경우 그 이름은 정의되지 않았거나 프로그래머에게는 어떤 가치도 없는 낯선 이름이다. (예를 들어, 자바에서 텍스트 노드의 nodeName 은 많은 경우 "#text"로 리포팅 된다. 근본적으로 에러 핸들링은 여러분에게 남겨지게 된다. 단순히 myNode.nodeName 에 액세스 하여 그 값을 사용하는 것은 안전한 방법이 아니다. 많은 경우 그 값은 무효가 될 것이다. 따라서 프로그래밍에 이것이 적용될 때 프로그래머들이 주의해야 한다.
일반 노드 유형들
DOM 노드의 기능과 속성에 대해 알아보았으니 이제 여러분이 작업 할 특정 유형의 노드에 대해 배울 차례이다. 대부분의 웹 애플리케이션에서 네 가지 유형의 노드로 작업한다.
- 문서 노드는 전체 HTML 문서를 나타낸다.
- 앨리먼트 노드는
a 또는 img 같은 HTML 엘리먼트를 나타낸다.
- 애트리뷰트 노드는
href (a 엘리먼트에 대해) 또는 src (img 엘리먼트에 대해) 같은 HTML 엘리먼트에 대한 애트리뷰트를 나타낸다.
- 텍스트 노드는 "Click on the link below for a complete set list" 같은 HTML 문서에 있는 텍스트를 나타낸다. 이는
p , a , h2 같은 엘리먼트 내부에 나타나는 텍스트이다.
HTML을 다룰 때 거의 모든 시간을 이러한 노드 유형으로 작업한다. 이제부터 이 부분을 상세히 설명하겠다. (향후 기술 자료에서는 XML에 대해 설명하겠다.)
문서 노드
첫 번째 노드 유형은 DOM 기반 코드의 거의 모든 부분에서 여러분이 사용하게 될 유형이다. 바로 문서 노드이다. 문서 노드는 실제로 HTML(또는 XML) 페이지에 있는 엘리먼트가 아니라 페이지 그 자체이다. 따라서 HTML 웹 페이지에서 문서 노드는 전체 DOM 트리이다. 자바스크립트에서 document 키워드를 사용하여 문서 노드에 액세스 할 수 있다.
// These first two lines get the DOM tree for the current Web page,
// and then the <html> element for that DOM tree
var myDocument = document;
var htmlElement = myDocument.documentElement;
|
자바스크립트의 document 키워드는 현재 웹 페이지에 대한 DOM 트리를 리턴한다. 여기에서부터 여러분은 트리에 있는 모든 노드들과 작업할 수 있다.
또한 document 객체를 사용하여 다음과 같은 메소드를 사용하는 새로운 노드를 만들 수 있다.
createElement(elementName) 는 제공된 이름을 가진 엘리먼트를 만든다.
createTextNode(text) 는 제공된 텍스트를 가진 새로운 텍스트 노드를 만든다.
createAttribute(attributeName) 는 제공된 이름을 가진 새로운 애트리뷰트를 만든다.
여기에서 주목해야 할 것은 이러한 메소드들이 노드를 만들지만 이들을 첨부하거나 이들을 특정 문서에 삽입하지 않는다는 점이다. 따라서 이미 봤던 insertBefore() 또는 appendChild() 같은 메소드들 중 하나를 사용해야 한다. 따라서 다음과 같은 코드를 사용하여 새로운 엘리먼트를 만들어 문서에 붙여야 한다.
var pElement = myDocument.createElement("p");
var text = myDocument.createTextNode("Here's some text in a p element.");
pElement.appendChild(text);
bodyElement.appendChild(pElement);
|
document 엘리먼트를 사용하여 웹 페이지의 DOM 트리에 액세스 하면 엘리먼트, 애트리뷰트, 텍스트와 직접 작업할 준비가 된 것이다.
엘리먼트 노드
엘리먼트 노드와 많이 작업했지만 엘리먼트에 대해 수행해야 하는 많은 연산들에는 엘리먼트에만 해당하는 메소드와 속성 보다는 모든 노드들에 공통적인 메소드와 속성들이 포함된다. 단 두 개의 메소드 세트만 엘리먼트에 관한 것이다.
- 애트리뷰트와 작동하는 것과 관련된 메소드: :
getAttribute(name) 는 name 이라는 애트리뷰트의 값을 리턴한다.
removeAttribute(name) 는 name 이라는 애트리뷰트를 제거한다.
setAttribute(name, value) 는 name 이라는 애트리뷰트를 만들고 이것의 값을 value 로 설정한다.
getAttributeNode(name) 는 name 라고 하는 애트리뷰트 노드를 리턴한다. (애트리뷰트 노드는 아래에서 설명한다.)
removeAttributeNode(node) 는 제공된 노드와 매치되는 애트리뷰트 노드를 제거한다.
- 중첩된 엘리먼트를 찾는 메소드: :
getElementsByTagName(elementName) 는 제공된 이름을 가진 엘리먼트 노드의 리스트를 리턴한다.
설명으로도 충분하지만 몇 가지 예제를 보도록 하자.
애트리뷰트 작업
애트리뷰트 작동은 매우 단순하다. 예를 들어, 도큐먼트 객체로 새로운 img 엘리먼트를 만들고, 엘리먼트, 위에서 몇 가지 메소드를 만든다.
var imgElement = document.createElement("img");
imgElement.setAttribute("src", "http://www.headfirstlabs.com/Images/hraj_cover-150.jpg");
imgElement.setAttribute("width", "130");
imgElement.setAttribute("height", "150");
bodyElement.appendChild(imgElement);
|
지금까지는 평범해 보인다. 사실 노드의 개념을 이해하고 사용할 수 있는 메소드를 알면 웹 페이지와 자바스크립트 코드에서 DOM으로 작업하는 것은 단순하다. 위 코드에서 자바스크립트는 새로운 img 엘리먼트를 만들고 애트리뷰트를 설정한 다음 이것을 HTML 페이지의 바디에 추가한다.
중첩 엘리먼트 찾기
중첩된 엘리먼트를 찾기도 쉽다. 예를 들어, Listing 3의 HTML 페이지에서 모든 img 엘리먼트를 찾아 제거하는데 사용했던 코드가 있다.
// Remove all the top-level <img> elements in the body
if (bodyElement.hasChildNodes()) {
for (i=0; i<bodyElement.childNodes.length; i++) {
var currentNode = bodyElement.childNodes[i];
if (currentNode.nodeName.toLowerCase() == "img") {
bodyElement.removeChild(currentNode);
}
}
}
|
getElementsByTagName() 을 사용하여 비슷한 효과를 얻을 수 있다.
// Remove all the top-level <img> elements in the body
var imgElements = bodyElement.getElementsByTagName("img");
for (i=0; i<imgElements.length; i++) {
var imgElement = imgElements.item[i];
bodyElement.removeChild(imgElement);
}
|
애트리뷰트 노드
DOM은 애트리뷰트를 노드로서 나타내고 여러분은 언제나 엘리먼트의 attributes 속성을 얻을 수 있다.
// Remove all the top-level <img> elements in the body
var imgElements = bodyElement.getElementsByTagName("img");
for (i=0; i<imgElements.length; i++) {
var imgElement = imgElements.item[i];
// Print out some information about this element
var msg = "Found an img element!";
var atts = imgElement.attributes;
for (j=0; j<atts.length; j++) {
var att = atts.item(j);
msg = msg + "\n " + att.nodeName + ": '" + att.nodeValue + "'";
}
alert(msg);
bodyElement.removeChild(imgElement);
}
|
|
애트리뷰트의 이상한 측면
애트리뷰트는 DOM에 있어서는 조금 특별하다. 애트리뷰트는 다른 엘리먼트나 텍스트의 경우와는 달리 엘리먼트의 자식이 아니다. 다시 말해서 엘리먼트의 밑에 나타나지 않는다. 동시에 이것은 엘리먼트와 관계를 갖고 있다. 엘리먼트는 자신의 애트리뷰트를 소유한다. DOM은 노드를 사용하여 애트리뷰트를 나타내고 이들을 특별한 리스트를 통해 엘리먼트에 대해 사용할 수 있도록 한다. 따라서 엘리먼트는 DOM 트리의 일부지만 이들은 트리에는 나타나지 않는다. DOM 트리 구조의 나머지에 대한 애트리뷰트의 관계는 다소 어지럽다. | |
attributes 속성은 실제로 엘리먼트 유형이 아닌 노드 유형에 있다. 이것은 코딩에 영향을 주지 않지만 알아둘 필요가 있다.
애트리뷰트 노드에서 작업하는 것이 가능하지만 엘리먼트 클래스에서 사용할 수 있는 메소드를 사용하여 애트리뷰트 작업을 하는 것이 더 쉽다. 메소드는 다음과 같다.
getAttribute(name) 는 name 이라는 애트리뷰트의 값을 리턴한다.
removeAttribute(name) 는 name 이라는 애트리뷰트를 제거한다.
setAttribute(name, value) 는 name 이라는 애트리뷰트를 만들고 이것의 값을 value 로 설정한다.
이 세 개의 메소드들로 인해 여러분은 애트리뷰트 노드와 직접 작업할 필요가 없다. 대신 애트리뷰트와 이것의 값과 함께 간단한 스트링 속성을 설정 및 제거할 수 있다.
텍스트 노드
여러분이 걱정하고 있는 마지막 노드 유형이자 HTML DOM 트리로 작업하는 유형은 텍스트 노드이다. 텍스트 노드와 작업하기 위해 공통적으로 사용하게 될 거의 모든 속성들은 실제로 노드 객체에서도 사용할 수 있다. 사실 nodeValue 속성을 사용하여 텍스트 노드에서 텍스트를 얻을 수 있다.
var pElements = bodyElement.getElementsByTagName("p");
for (i=0; i<pElements.length; i++) {
var pElement = pElements.item(i);
var text = pElement.firstChild.nodeValue;
alert(text);
}
|
몇 가지 다른 메소드들은 텍스트 노드만의 것이다. 이들은 노드에 있는 데이터를 추가하거나 쪼갠다.
appendData(text) 는 여러분이 제공한 텍스트를 텍스트 노드의 기존 텍스트의 끝에 추가한다.
insertData(position, text) 는 텍스트 노드의 중간에 데이터를 삽입할 수 있다. 이것은 지정된 위치에 여러분이 제공한 텍스트를 삽입한다.
replaceData(position, length, text) 는 지정된 위치부터 시작하여 지정된 길이의 문자를 제거하고 여러분이 제공한 텍스트를 제거된 텍스트를 대신하여 메소드에 둔다.
노드의 유형
여러분이 작업할 노드 유형이 무엇인지에 대해 이미 알고 있다는 전제 하에 설명을 해왔다. 만일 DOM 트리를 통해 검색하고 일반 노드 유형들로 작업한다면 엘리먼트나 텍스트로 이동했는지의 여부를 모를 것이다. 아마도 p 엘리먼트의 모든 자식들을 갖게 되고, 텍스트, b 엘리먼트, 아니면 img 엘리먼트로 작업하는지 확신할 수 없다. 이 경우 더 많은 일을 하기 전에 어떤 유형의 노드를 가졌는지를 규명해야 한다.
다행히도 이것을 파악하기는 매우 쉽다. DOM 노드 유형은 여려 상수들을 정의한다.
Node.ELEMENT_NODE 는 엘리먼트 노드 유형에 대한 상수이다.
Node.ATTRIBUTE_NODE 는 애트리뷰트 노드 유형에 대한 상수이다.
Node.TEXT_NODE 는 텍스트 노드 유형에 대한 상수이다.
Node.DOCUMENT_NODE 는 문서 노드 유형에 대한 상수이다.
다른 노드 유형들도 많이 있지만 HTML을 처리할 때에는 이 네 가지 외에는 별로 다루지 않는다. 이들 상수의 값이 DOM 스팩으로 정의되었지만 의도적으로 남겨두었다. 여러분은 이 값으로 절대 직접 처리해서는 안된다. 바로 이것 때문에 상수가 있는 것이니까.
nodeType 속성
DOM 노드 유형에 대해 정의되었기 때문에 모든 노드에서 사용할 수 있는 nodeType 속성을 사용하여 노드를 위 상수와 비교할 수 있다.
var someNode = document.documentElement.firstChild;
if (someNode.nodeType == Node.ELEMENT_NODE) {
alert("We've found an element node named " + someNode.nodeName);
} else if (someNode.nodeType == Node.TEXT_NODE) {
alert("It's a text node; the text is " + someNode.nodeValue);
} else if (someNode.nodeType == Node.ATTRIBUTE_NODE) {
alert("It's an attribute named " + someNode.nodeName
+ " with a value of '" + someNode.nodeValue + "'");
}
|
이것은 매우 단순한 예제이지만 포인트가 있다. 노드의 유형을 얻는 것은 단순하다. 일단 어떤 유형인지를 알면 노드로 무엇을 할 것인지를 규명해야 한다. 하지만 노드, 텍스트, 애트리뷰트, 엘리먼트 유형이 무엇을 제공하는지 확실히 안다면 DOM 프로그래밍을 직접 할 준비가 된 것이다.
작업의 고통
nodeType 속성이 노드로 작업할 수 있는 티켓인 것처럼 들린다. 이것으로 여러분은 어떤 유형의 노드로 작업하고 있는지를 규명하고 그 노드를 다룰 코드를 작성할 수 있다. 문제는 위에 정의된 Node 상수들이 Internet Explorer 상에서는 올바르게 작동하지 않는다는 점이다. 따라서 Node.ELEMENT_NODE , Node.TEXT_NODE 또는 코드의 다른 상수를 사용한다면 Internet Explorer는 그림 4와 같은 에러를 리턴할 것이다.
그림 4. Internet Explorer의 에러 리포팅
Internet Explorer는 여러분이 자바스크립트에서 Node 상수를 사용할 때 마다 이러한 에러를 보고 할 것이다. 거의 모든 사람들이 Internet Explorer를 사용하기 때문에 Node.ELEMENT_NODE 또는 Node.TEXT_NODE 같은 구조를 피해야 한다. Internet Explorer 7.0이 이러한 문제를 정정했다 하지만 Internet Explorer 6.x의 대중성에 미치려면 오랜 시간이 남았다. 따라서 Node 사용을 피해라. 여러분의 DOM 코드(그리고 Ajax 애플리케이션)가 모든 주요 브라우저에서 작동해야 하기 때문이다.
결론
|
정상에 설 준비가 되었나요?
여러분이 진정 DOM을 완벽히 이해했다면 웹 프로그래밍 기술 레벨의 꼭대기에 오르게 될 것이다. 대부분의 웹 프로그래머들은 자바스크립트를 사용하여 이미지 롤오버를 작성하는 방법, 폼에서 값을 얻는 방법을 알고 있다. 심지어 서버에서 요청을 만들고 응답을 받는 사람들도 있다. 하지만 웹 페이지의 구조를 바꾸는 일은 소심한 사람이나 경험 없는 사람들은 피해주기 바란다. | |
본 시리즈를 통해 많은 것을 배웠다. 오늘 배운 것을 그냥 머리로 익히지만 말고 DOM 트리를 직접 사용해 볼 것을 권한다. DOM을 사용하여 멋진 효과나 그럴듯한 인터페이스를 만드는 방법을 연구해 보라. 여러분에게 내주는 숙제이다. 지난 두 개의 기술자료에서 배운 것을 실험해 보기 바란다. 사용자 액션에 대한 응답으로 스크린 상을 돌아다니는 것 같은 웹 사이트를 만든다는 것을 상상해 보라.
스크린 상의 모든 객체 주위의 보더를 없애기 때문에 여러분은 DOM 트리에서 객체들이 어디에 있는지 볼 수 있고 움직일 수 잇다. 노드를 만들고 이것을 기존 자식 리스트에 붙인다. 많은 중첩 노드들을 가진 노드들을 제거한다. CSS 스타일을 노드를 변경하고 그러한 변화가 자식 노드에 상속되었는지를 확인한다. 가능성은 무한하다. 새로운 것을 시도할 때 마다 새로운 것을 배운다. 웹 페이지를 즐겨라.
다음 글에서는 DOM의 멋진 애플리케이션들을 여러분의 프로그래밍과 결합하는 방법을 설명할 것이다. API에 대한 개념적인 설명은 그만두고 코드를 보여주겠다. 그때까지 자신 스스로 어떻게 할 것인지를 생각해 보라.
기사의 원문보기
참고자료 교육
제품 및 기술 얻기
토론
필자소개
|
|
|
Brett McLaughin은 Logo 시절부터 컴퓨터 업계에서 일했다 (작은 트라이앵글 기억하는가?). 최근에 그는 Java 및 XML 커뮤니티에서 인기 저자 및 프로그래머가 되었다. Nextel Communication사에서는 복잡한 기업 시스템 실행에 관한 업무, Lutris Technologies 사에선 애플리케이션 서버를 실지로 작성하는 업무, 최근 O'Reilly Media 사에서는 이와 관련된 중요한 책을 저술, 편집했다. Brett의 최근 저서인 Head Rush Ajax는 Ajax에 관한 혁신적인 연구에 기여, 공동 저자인 Eric 및 Beth Freeman과 함께 공동으로 수상했다. 그의 최근 저서인 Java 1.5 Tiger: A Developer's Notebook은 신 자바 기술 버전 상에서 이용 가능한 첫 번째 저서다. Brett의 최신 Java 및 XML은 자바 언어에서 XML 기술을 활용한 명백한 업적 중 하나로 남아 있다. | |