Secrets of the JavaScript Ninja - Chapter 12. DOM modification

  • 2,329 views
Uploaded on

"Secrets of the JavaScript Ninja"에서 12장 : DOM 조작에 대해 설명한다. …

"Secrets of the JavaScript Ninja"에서 12장 : DOM 조작에 대해 설명한다.
* 정리 된 위키 : http://goo.gl/VfjiM

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
2,329
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
12
Comments
0
Likes
2

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. 12DOM modificationSecrets of the JavaScript Ninja
  • 2. • HTML 문자들을 페이지에 넣기 • 엘리먼트 복제 • 엘리먼트 제거 • 엘리먼트 텍스트 조작이 챕터에서 다루는 내용:
  • 3. 12.0 들어가며 DOM을 순차적으로 처리하는데는 일반적인 과정의 JavaScript 코드가 필요주로 새로운 노드를 계속해서 document에 추가, 복제, 제거하는데 도움이 되는 코드들이 있겠다.
  • 4. 12.1 Injecting HTML insertAdjacentHTML 1. 오직 IE에만 존재 (따라서 대안책이 필요) 2. IE의 구현은 매우 buggy (일부 엘리먼트에서 만 동작)우선 효과적인 방법으로 HTML 문자열을 document 임의의 위치에 넣는걸 보자.Internet Explorer에 있는 API (W3C HTML 5 스펙에 포함: http://www.w3.org/TR/html5/apis-in-html-documents.html#insertadjacenthtml) 하지만 몇 가지 문제가 있다.
  • 5. 12.1 Injecting HTML 1. 임의의, 유효한, HTML/XHTML 문자열을 DOM 구조체로 변환한다. 2. 가능하면 효과적으로 DOM 구조체를 임 의의 위치에 넣는다. 3. 문자열의 inline script를 실행한다.때문에 처음부터 다시 깔끔한 API를 만들어야한다.
  • 6. 12.1.1 Converting HTML to DOM • HTML 문자열이 확실하게 HTML/XHTML에 대 해 유효한지 확인 (최대한 valid 하도록 조작) • 문자열엔 감싸여진 마크업이 필요 • innerHTML 을 사용하여 dummy DOM 엘리먼트 에 추가 • DOM 노드를 다시 뽑아냄딱히 마법과 같은게 아니라 우리가 익히 알고 있는 정밀한 도구: innerHTML 를 사용한다.
  • 7. Pre-Process XML/HTML • 문맥에 따라 달라지는 경우 • jQuery는 <table/> 같은 XML 스타일 엘리먼트를 지원 • 브라우저에서는 (IE 같은) 일부분의 HTML 엘리 먼트만 XML 스타일만 동작 • pre-parse 통해 예방string을 미리 가공하는 준비 단계
  • 8. Pre-Process XML/HTML// Listing 12.1 : http://jsbin.com/izisarvar tags = /^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i;function convert(html){ return html.replace(/(<(w+)[^>]*?)/>/g, function(all, front, tag){ return tags.test(tag) ? all : front + "></" + tag + ">"; });}assert( convert("<a/>") === "<a></a>", "Check anchor conversion." );assert( convert("<hr/>") === "<hr/>", "Check hr conversion." );
  • 9. HTML Wrapping • 특정 parent에 문자열을 바로 넣을 수 있다. • 허나 모든 환경에서 보장되지 않는다.대다수의 HTML 엘리먼트는 꼭 컨테이너 엘리먼트가 있어야한다. (예를 들어 option 태그) 두 가지 해결책을 보자.
  • 10. HTML Wrapping • 적합한 마크업으로 감싸져있을 땐 어느 컨테이 너 엘리먼트에든 넣을 수 있다. option, optgroup : <select multiple="multiple">...</select> legend : <fieldset>...</fieldset> thead, tbody, tfoot, colgroup, caption : <table>...</table> tr : <table><thead>...</thead></table> , <table><tbody>...</tbody></table> , <table><tfoot>...</tfoot></table> td, th : <table><tbody><tr>...</tr></tbody></table> col : <table><tbody></tbody><colgroup>...</colgroup></table> link, script : <div>...</div>■ multiple select 를 사용한 이유는 inject 과정에서 자동으로 첫번째 option이 선택되는걸 방지하기 위해.■ col 태그를 위해 추가적인 tbody가 없으면 생성되지 않을 수 있다.■ link, script 경우 IE에서 앞뒤에 노드없이 innerHTML 사용하면 엘리먼트를 생성해내지 못한다.
  • 11. Generating the DOM // Listing 12.2 : http://jsbin.com/ohevux function getNodes(htmlString){ var map = { "<td": [3, "<table><tbody><tr>", "</tr></tbody></table>"], "<option": [1, "<select multiple=multiple>", "</select>"] // a full list of all element fixes }; var name = htmlString.match(/<w+/), node = name ? map[ name[0] ] : [0, "", ""]; var div = document.createElement("div"); div.innerHTML = node[1] + htmlString + node[2]; while ( node[0]-- ) div = div.lastChild; return div.childNodes; } assert( getNodes("<td>test</td><td>test2</td>").length === 2, "Get two nodes back from the method." ); assert( getNodes("<td>test</td>")[0].nodeName === "TD", "Verify that were getting the right node." );앞의 wrapping 을 직접 적용해보자.IE에서는 아래의 버그가 있다. 1. 빈 table에 tbody를 추가해버린다. 2. innerHTML으로 입력 된 문자열의 행간 공백을 모두 제거해버린다.지금까지 과정을 거침으로써 이제 document에 추가할 준비가 되었다.
  • 12. 12.1.2 Inserting into the Document DOM Fragments • W3C DOM 스펙이며 모든 브라우저에서 지원 • DOM 노드들을 담아 둘 수 있는 컨테이너를 제공 • 장점1 : 간단한 명령으로 inject, clone 가능 • 장점2 : 반복하여 inject, clone 가능우선 효과적인 방법으로 HTML 문자열을 document 임의의 위치에 넣는걸 보자.Internet Explorer에 있는 API (W3C HTML 5 스펙에 포함: http://www.w3.org/TR/html5/apis-in-html-documents.html#insertadjacenthtml) 하지만 몇 가지 문제가 있다.
  • 13. // Listing 12.3 : http://jsbin.com/uyuwuz/2 // <div id="test"><b>Hello</b>, Im a ninja!</div> // <div id="test2"></div> window.onload = function(){ function insert(elems, args, callback){ if ( elems.length ) { var doc = elems[0].ownerDocument || elems[0], fragment = doc.createDocumentFragment(), scripts = getNodes( args[0], doc, fragment ), first = fragment.firstChild; if ( first ) { for ( var i = 0; elems[i]; i++ ) { callback.call( root(elems[i], first), i > 0 ? fragment.cloneNode(true) : fragment ); } } } } var divs = document.getElementsByTagName("div"); insert(divs, ["<b>Name:</b>"], function(fragment){ this.appendChild( fragment ); }); insert(divs, ["<span>First</span> <span>Last</span>"], function(fragment){ this.parentNode.insertBefore( fragment, this ); }); };jQuery 코드를 참조해보면, fragment가 재생성 되어 함수에 전달되는 걸 확인할 수 있다.
  • 14. 12.1.2 Inserting into the Document // Listing 12.4 : http://jsbin.com/uyuwuz/2 function root( elem, cur ) { return elem.nodeName.toLowerCase() === "table" && cur.nodeName.toLowerCase() === "tr" ? (elem.getElementsByTagName("tbody")[0] || elem.appendChild(elem.ownerDocument.createElement("tbody"))) : elem; }마지막으로 사용자가 직접 table 넣기를 시도할 경우 tbody를 매핑하는 식으로 관리해준다.
  • 15. 12.1.3 Script Execution • 인라인 스크립트 엘리먼트가 document에 추가되면 실행이 되어야 할 것이다. • 가장 좋은 방법은 document에 추가되기 전에 script 들을 분리시켜 놓는 방법이다.
  • 16. 12.1.3 Script Execution // Listing 12.5 : http://jsbin.com/atujam for ( var i = 0; ret[i]; i++ ) { if ( jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); } else if ( ret[i].nodeType === 1 ) { ret.splice.apply( ret, [i + 1, 0].concat( jQuery.makeArray(ret[i].getElementsByTagName("script"))) ); } }ret (생성 될 DOM 노드), scripts (스크립트들을 fragment로 모음)의 2가지 배열로 분리. 이제 교묘한 방법으로 스크립트들을 실행해보자.
  • 17. Global Code Evaluation • 사용자가 정의한 인라인 스크립트들은 global context로 실행 된다. • Andrea Giammarchi 가 착안한 스크립트 실 행법을 이용한다. - document에 script 엘리 먼트를 붙였다 때어내는 방식
  • 18. Global Code Evaluation // Listing 12.6 : http://jsbin.com/orevuz function globalEval( data ) { data = data.replace(/^s+|s+$/g, ""); if ( data ) { var head = document.getElementsByTagName("head")[0] || document.documentElement, script = document.createElement("script"); script.type = "text/javascript"; script.text = data; head.insertBefore( script, head.firstChild ); head.removeChild( script ); } }"어때요? 참~ 쉽죠?" 이제 이를 활용하여 동적 로딩까지 되는 코드를 만들 수 있습니다.
  • 19. Global Code Evaluation // Listing 12.7 : http://jsbin.com/uvinos function evalScript( elem ) { if ( elem.src ) jQuery.ajax({ url: elem.src, async: false, dataType: "script" }); else jQuery.globalEval( elem.text || "" ); if ( elem.parentNode ) elem.parentNode.removeChild( elem ); } NOTE 실행이 완료 된 스크립트는 DOM에서 제거 합니다. (나중에 의도치 않는 이중 실행을 방지)"어때요? 참~ 쉽죠?" 이제 이를 활용하여 동적 로딩까지 되는 코드를 만들 수 있습니다.
  • 20. 12.2 Cloning Elements • 엘리먼트 복제(DOM cloneNode 메소드 사용)는 모든 브라우저에서 직접 사용 된 다. • IE는 3가지 절망적인 단계를 거쳐야한다.
  • 21. 12.2 Cloning Elements • 첫째, 엘리먼트 복제를 하면 이에 따른 모든 이벤트 헨들러를 복제한다. // Listing 12.8 : http://jsbin.com/atagec var div = document.createElement("div"); if ( div.attachEvent && div.fireEvent ) { div.attachEvent("onclick", function(){ // Cloning a node shouldnt copy over any // bound event handlers (IE does this) jQuery.support.noCloneEvent = false; div.detachEvent("onclick", arguments.callee); }); div.cloneNode(true).fireEvent("onclick"); }
  • 22. 12.2 Cloning Elements • 둘째, 복제 된 엘리먼트에서 이벤트 헨들러를 제거하 면 본래 엘리먼트쪽이 제거된다. • 셋째, 이를 해결하는 단계는 또다른 엘리먼트에 넣은 다음, innerHTML으로 읽어오고, 그리고 다시 DOM 노 드로 변환하는 것이다.3 - 이 때 또다른 IE 버그 : innerHTML (또는 outerHTML) 읽어올 때 항상 정확한 엘리먼트 속성을 유지하지는 않는다. 때문에 XML DOM 확인 분기가 추가.
  • 23. 12.2 Cloning Elements// Listing 12.9 : http://jsbin.com/etegehfunction clone() { var ret = this.map(function(){ if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { var clone = this.cloneNode(true), container = document.createElement("div"); container.appendChild(clone); return jQuery.clean([container.innerHTML])[0]; } else return this.cloneNode(true); }); var clone = ret.find("*").andSelf().each(function(){ if ( this[ expando ] !== undefined ) this[ expando ] = null; }); return ret;}
  • 24. 12.3 Removing Elements • 연결되어 있는 이벤트 헨들러를 모두 정 리해야 한다. 이 단계는 IE의 메모리릭 때 문에 매우 중요하다. • 연결되어 있는 외부 데이터를 정리해야 한다.DOM에서 엘리먼트 제거하기는 간단하다. (removeChild 바로 사용) 제거하는 과정엔 주로 2단계가 필요하다....위 내용들은 추후 이벤트 챕터에서 다루기로 하자.
  • 25. 12.3 Removing Elements // Listing 12.10 : http://jsbin.com/ivaguq function remove() { // Go through all descendants and the element to be removed jQuery( "*", this ).add([this]).each(function(){ // Remove all bound events jQuery.event.remove(this); // Remove attached data jQuery.removeData(this); }); // Remove the element (if its in the DOM) if ( this.parentNode ) this.parentNode.removeChild( this ); }위의 포인트들이 엘리먼트 - 자손도 포함한 제거 과정에서 진행되는 jQuery 코드를 보자.
  • 26. 12.3 Removing Elements // Listing 12.11 : http://jsbin.com/uzuhuc // Remove the element (if its in the DOM) if ( this.parentNode ) this.parentNode.removeChild( this ); if ( typeof this.outerHTML !== "undefined" ) this.outerHTML = ""; NOTE 기억하세요! 항상 DOM을 tidy하게 유지해야 나중에 메모리 이슈를 덜어줍니다.또다른 고려사항은 정리 후에 실제로 DOM에서 엘리먼트가 없어졌는지 봐야한다. (역시 IE에서만 예외적) 잘 동작하는 해결책으로는 IE에서 제공하는 outerHTML 을 활용하는방법.
  • 27. 12.4 Text Contents • W3C-compliant 브라우저에서는 textContent 속성 사용 (자식, 자손 노드 모두 적용) • IE에서는 innerText, textContent 속성 사용
  • 28. // Listing 12.12 : http://jsbin.com/olanub/2 // <div id="test"><b>Hello</b>, Im a ninja!</div> // <div id="test2"></div> window.onload = function(){ var b = document.getElementById("test"); var text = b.textContent || b.innerText; assert( text === "Hello, Im a ninja!", "Examine the text contents of an element." ); assert( b.childNodes.length === 2, "An element and a text node exist." ); if ( typeof b.textContent !== "undefined" ) { b.textContent = "Some new text"; } else { b.innerText = "Some new text"; } text = b.textContent || b.innerText; assert( text === "Some new text", "Set a new text value." ); assert( b.childNodes.length === 1, "Only one text nodes exists now." ); };Note : textContent/innerText 속성 사용 시 내부의 본래 엘리먼트 구조는 사라진다.
  • 29. 12.4 Text Contents • 여기서 발생하는 gotchas 1. 엘리먼트가 사라지는 경우 앞서 언급했던 메모리릭 발생 2. whitespace 크로스브라우징 처리 경우 최악
  • 30. Setting Text • 내부의 엘리먼트가 비워지고, 새로운 텍스트가 입력된다. • 내부의 컨텐츠가 비워진다. - Listing 12.10
  • 31. Setting Text // Listing 12.13 : http://jsbin.com/otosuv/2 // <div id="test"><b>Hello</b>, Im a ninja!</div> // <div id="test2"></div> window.onload = function(){ var b = document.getElementById("test"); // Replace with your empty() method of choice while ( b.firstChild ) b.removeChild( b.firstChild ); // Inject the escaped text node b.appendChild( document.createTextNode( "Some new text" ) ); var text = b.textContent || b.innerText; assert( text === "Some new text", "Set a new text value." ); assert( b.childNodes.length === 1, "Only one text nodes exists now." ); };HTML과 텍스트 입력의 차이점 : HTML 고유 문자가 escape 된다. 때문에 createTextNode 메소드를 사용이 필요하다.
  • 32. Getting Text • endline 관련 문제들 때문에 textContent/ innerText 사용은 하지 않는다. • 대신 텍스트 노드의 값들을 직접 읽어오 는 것이 정확한 값을 가져오겠다.1 - (해당 문제를 딱히 신경 쓸 필요 없다면 그냥 사용하는게 간단)
  • 33. // Listing 12.14 : http://jsbin.com/olanof/2// <div id="test"><b>Hello</b>, Im a ninja!</div>// <div id="test2"></div>window.onload = function(){ function getText( elem ) { var text = ""; for ( var i = 0, l = elem.childNodes.length; i < l; i++ ) { var cur = elem.childNodes[i]; // A text node has a nodeType === 3 if ( cur.nodeType === 3 ) text += cur.nodeValue; // If its an element we need to recurse further else if ( cur.nodeType === 1 ) text += getText( cur ); } return text; } var b = document.getElementById("test"); var text = getText( b ); assert( text === "Hello, Im a ninja!", "Examine the text contents of an element." ); assert( b.childNodes.length === 2, "An element and a text node exist." );};
  • 34. 12.5 Summary • DOM 조작에 있어 어려운점과 이를 해결 하는 방법을 포괄적으로 살펴 보았다. • 크로스브라우징 이슈를 설명하고 실제 로 구현하는게 훨씬 힘들다. • 잘 동작하는 통합 된 솔루션을 만들기 위 해 노력해야겠다.