ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JS/DOM] 자바스크립트, 문서 객체 모델(Document Object Model) 조작하기(2) - DOM Manipulation
    Frontend 2019. 6. 16. 21:25

    오늘은 자바스크립트로 DOM을 동적 생성하는 방법에 대해서 정리해보려고 한다.

    정리를 위해서 간단한(...간단하지만 나름 깔끔하게 만들려고 오랜 시간 고군분투했다.히히)

    영화 정보를 출력하는 웹 페이지를 만들어보기로 했다.

     

     

     

     

    영화 정보를 가져오기 위해서 인터넷을 뒤져서 146개의 영화 정보를 담고 있는 JSON 파일을 다운 받았다.

     

    JSON 파일을 연결하는 방법은 아직 모르니까

    일단은 js 파일에 MOVIES라는 이름의 객체로 만들어 html파일에 연결시켜주었다.

     

    const MOVIES = [{
        "id": 1,
        "title": "Beetlejuice",
        "year": "1988",
        "runtime": "92",
        "genres": [
          "Comedy",
          "Fantasy"
        ],
        "director": "Tim Burton",
        "actors": "Alec Baldwin, Geena Davis, Annie McEnroe, Maurice Page",
        "plot": "A couple of recently deceased ghosts contract the services of a \"bio-exorcist\" in order to remove the obnoxious new owners of their house.",
        "posterUrl": "https://images-na.ssl-images-amazon.com/images/M/MV5BMTUwODE3MDE0MV5BMl5BanBnXkFtZTgwNTk1MjI4MzE@._V1_SX300.jpg"
      },
      {
        "id": 2,
        "title": "The Cotton Club",
        "year": "1984",
        "runtime": "127",
        "genres": [
          "Crime",
          "Drama",
          "Music"
        ],
        "director": "Francis Ford Coppola",
        "actors": "Richard Gere, Gregory Hines, Diane Lane, Lonette McKee",
        "plot": "The Cotton Club was a famous night club in Harlem. The story follows the people that visited the club, those that ran it, and is peppered with the Jazz music that made it so famous.",
        "posterUrl": "https://images-na.ssl-images-amazon.com/images/M/MV5BMTU5ODAyNzA4OV5BMl5BanBnXkFtZTcwNzYwNTIzNA@@._V1_SX300.jpg"
      }, {
      	// ...
      }];

     

    MOVIES는 대충 위와 같은 형태로 생겼다.

     

     

    <div class="container">
      <header>
          <h1 class="title">Movie List</h1>
      </header>
    
      <ul class="movie-list">
        <li class="clearfix">
          <div class="movie-img"></div>
          <div class="movie-info">
            <h2 class="movie-title">The Lives of Others</h2>
            <dl class="info-list">
              <dt>genres</dt>
              <dd>
                Drama, History, War
                <span class="etc">121min</span>
                <span class="etc">2004</span>
              </dd>
            </dl>
            <dl class="info-list">
              <dt>director</dt>
              <dd>Terry George</dd>
            </dl>
            <dl class="info-list">
              <dt>actors</dt>
              <dd>Xolani Mali, Don Cheadle, Desmond Dube, Hakeem Kae-Kazim</dd>
            </dl>
            <dl class="info-list">
              <dt>plot</dt>
              <dd class="txt-ellipsis">
                Paul Rusesabagina was a hotel manager who housed over a thousand Tutsi refugees during their struggle against the Hutu militia in Rwanda.
              </dd>
            </dl>
          </div>
        </li>
      </ul>
    </div>

     

    ul.movie-list에서 li 태그를 동적으로 생성할 것이다.

    어떻게 만들지 미리 html 마크업으로 뼈대를 잡아준다.

    나중에 자바스크립트로 생성하고 난 후 li 태그는 전부 지울 것이다.

     

     

    * { box-sizing: border-box; }
    html, body { font-family: 'News Cycle', sans-serif; }
    ul { list-style: none; margin: 0; padding: 0; }
    .clearfix:after { content: ''; float: none; clear: both; display: block; }
    .sr-only { display: inline-block; text-indent: -10000px; width: 1px; height: 1px; }
    .container { width: 1100px; margin: auto; }
    header { border-bottom: 1px solid #333; margin-bottom: 50px;  }
    .title { font-size: 60px; text-align: center; }
    
    .movie-list { display: flex; flex-wrap: wrap; }
    .movie-list li { flex-basis: 50%; padding: 20px 10px; }
    .movie-list li .movie-img { width: 30%; height: 100%; float: left; }
    .movie-list .movie-info { float: right; width: 70%; padding-left: 20px; }
    .movie-list .movie-title { margin: 0 0 15px 0; font-size: 30px; }
    .movie-list dl { display: flex; align-items: baseline; margin: 0 0 7px 0; }
    .movie-list dt { flex-basis: 80px; font-weight: bold; font-size: 18px; }
    .movie-list dd { margin-left: 0; flex-basis: calc(100% - 80px); line-height: 1.3; }
    .movie-list .etc { margin-left: 5px; }
    .movie-list .etc:before { content: '|'; margin-right: 7px; color: #ccc; }
    .txt-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: normal; height: 2.6em; word-wrap: break-word; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }

     

    css로 모양을 잡아준다.

     

    지금까지 한 번도 flex를 사용해본 적이 없는 데 오늘 연습삼아 처음으로 써봤다. 근데 진짜 신세계였다.

    익스플로러와 많은 구형 브라우저에서 flex를 지원하지 않기 때문에 아직 실무에선 사용하기 어려운 것이 너무 아쉽다.

     

     

    const $ul = document.querySelector('.movie-list');
    
    MOVIES.forEach(function (movie) {
      
    });

     

    이제 MOVIES 배열에 forEach() 메소드를 사용하여 배열 개수만큼 li 태그를 동적으로 생성하여

    $ul의 자식 요소로 넣어줄 것이다.

     

     

     

    1. DOM 조작 방식

    DOM을 직접 조작하여 생성하는 방식은 주로 한 두개의 요소를 추가하는 경우 사용된다.

     

    1. 먼저 createElement() 메소드에 태그 이름을 인자로 주어 element를 생성한다.

    2. createTextNode() 메소드로 태그 안에 넣을 text 노드를 생성한다.

    3. appendChild() 메소드로 text 노드를 element 노드에 추가하고 element 노드를 원하는 노드의 하위 요소로 추가한다.


     

    document.createElement(tagName);

    태그 이름을 인자로 전달받아 요소를 생성한다.

    const $li = document.createElement('li');

     

    document.createTextNode(text);

    텍스트를 인자로 전달받아 텍스트 노드를 생성한다.

    const text = document.createTextNode('hello');

     

    parentElement.appendChild(childNode);

    인자로 전달한 노드를 지정한 부모 요소의 마지막 자식 노드로 DOM 트리에 추가한다.

    $li.appendChild(text);
    
    const $ul = document.querySelector('ul');
    $ul.appendChild($li);

     

    parentElement.removeChild(childNode);

    인자로 전달한 노드를 DOM 트리에서 제거한다.

    $ul.removeChild($li);

     


     

    ※ 태그에 원하는 속성 추가하기:

     

    Element.setAttribute('href', '#');

    해당 엘리먼트에 원하는 속성을 추가할 때 사용한다.

    a 태그에 href 속성이나 img 태그에 src 속성 등 무궁무진하게 사용 가능하다.

    setAttribute() 메소드를 사용하지 않고 태그에 속성을 그냥 지정하는 방법도 있다. (아래 참조)

     

    Element.getAttribute('href');

    해당 엘리먼트에서 인자로 넘겨준 속성의 값을 가져온다.

     

    Element.hasAttribute('href');

    해당 엘리먼트에 인자로 넘겨준 속성이 있는지 체크하여 boolean값을 반환한다.

     


     

    ※ 기타 유용한 속성들:

     

    Element.dataset.name = 'value';

    해당 엘리먼트에 data-attribute를 생성한다.

    setAttribute() 메소드를 사용하여 지정할 수도 있으나 dataset 속성으로 추가하는 것이 훨씬 편하다.

     

    element.className = 'class-name class-name2';

    해당 엘리먼트에 해당 클래스명을 지정한다. 공백으로 구분하여 여러 개의 클래스명을 지정할 수 있다.

     

    element.src = 'image path';

    이미지 태그에 이미지 경로 속성을 추가한다. setAttribute() 메소드를 사용하는 대신 이런 식으로 속성에 값을 할당하는 방법으로 속성을 추가할 수 있다.

     

     

     

    이러한 DOM 조작 방식의 문제점은 DOM을 만드는 데 너무나도 많은 코드가 필요하다는 것이다.

    DOM 트리 구조가 간단하다면 상관없지만 복잡한 구조를 DOM 조작 방식으로 만들려면

    아주 긴 코드가 필요하고 그 때문에 속도도 상대적으로 느리다.

     

    그 때 사용할 수 있는 것이 innerHTML이다.

     


     

    Element.innerHTML = '<span>text</span>';

    HTML 마크업을 포함하여 텍스트 등 해당 요소 하위의 모든 콘텐츠를 하나의 문자열로 취득할 수 있다.

    const $ul = document.querySelector('ul');
    const $li = '<li><span class="number">1</span>번 리스트</li><li>2번 리스트</li><li>3번 리스트</li>';
    $ul.innerHTML = $li;

     

    Element.innerText = 'text';

    document.createTextNode() 메소드를 사용하지 않고 innerText로 텍스트 콘텐츠에 접근할 수 있다. 그러나 innerText는 비표준이며 CSS의 영향을 받아 속도가 느리다. 예를 들면 innerText는 텍스트가 보이지 않도록 visibility: hidden;이 지정되어 있는 경우에 텍스트 값을 가져오지 못한다. 그러므로 innerText 대신 textContent를 쓰는 것이 바람직하다.

     

    Element.textContent = 'text';

    요소의 텍스트 콘텐츠를 넣거나 수정할 수 있다. innerHTML과 달리 마크업을 포함하여도 문자열로만 인식한다.

    $span = document.querySelector('span');
    $span.textContent = 'text';

     

     

     

    innerHTML은 어떤 요소에 innerHTML을 이용하여 하위 노드를 추가하면 기존에 존재하던 하위 노드들이 모두 제거되고 새로운 노드로 덮어써진다는 특징이 있다. <div><span>내용</span></div> 이러한 마크업을 하고 div 요소에 innerHTML로 새로운 요소를 넣으면 span 태그가 제거되고 새로 추가한 노드만 남게된다.

     


     

    Element.insertAdjacentHTML(position, 'text');

    innerHTML과는 달리 해당 요소 하위에 존재하는 노드들을 제거하지 않고 지정하는 위치에 새로운 노드를 삽입할 수 있다. 첫 번째 인자로 새로운 노드를 추가할 위치를 지정해줄 수 있는 데, 4가지로 지정해줄 수 있다.

     

    'beforebegin'

    'afterbegin'

    'beforeend'

    'afterend'

     

    <div>
      <h2>제목</h2>
    </div>

     

    위와 같은 상황에서 div 안에 p 태그를 추가하려고 할 때, position을 어떻게 지정하냐에 따라서 p가 추가되는 위치가 지정된다.

     

     


     

     

    이제 위에서 살펴본 DOM 생성 및 조작 방법을 사용하여 MOVIE LIST를 완성해보았다.

     

    const $ul = document.querySelector('.movie-list');
    
    MOVIES.forEach(function (movie) {
      const $li = document.createElement('li');
      const $img = document.createElement('div');
    
      $li.dataset.id = `movie-${movie.id}`;
      $li.className = 'clearfix';
      $img.className = 'movie-img';
      $img.style.background = `#eee url(${movie.posterUrl}) no-repeat center / cover`;
      
      const $srOnly = `<span class="sr-only">${movie.title} poster</span>`;
      $img.innerHTML = $srOnly;
      
      const $movieInfo = document.createElement('div');
      $movieInfo.className = 'movie-info';
    
      const $h2 = document.createElement('h2');
      $h2.className = 'movie-title';
      $h2.textContent = movie.title;
    
      const $infoList = '<dl class="info-list"><dt>genres</dt>'
          + `<dd>${movie.genres.join(', ')}<span class="etc">${movie.runtime}min</span><span class="etc">${movie.year}</span></dd></dl>`
          + '<dl class="info-list"><dt>director</dt>'
          + `<dd>${movie.director}</dd></dl>`
          + '<dl class="info-list"><dt>actors</dt>'
          + `<dd>${movie.actors}</dd></dl>`
          + '<dl class="info-list"><dt>plot</dt>'
          + `<dd class="txt-ellipsis">${movie.plot}</dd></dl>`;
      
      $movieInfo.appendChild($h2);
      $movieInfo.insertAdjacentHTML('beforeend', $infoList);
      
      $li.appendChild($img);
      $li.appendChild($movieInfo);
      
      $ul.appendChild($li);
    });

    (만약에 위에 $infoList에 있는 내용을 DOM 조작 방식으로만 만든다고 생각하면 너무 끔찍하다!)

     

     

     

    반응형

    COMMENT