템플릿(Template)

3

번역 완료율

이 장에서는:

  • 미티어의 템플릿 언어인 Spacebars에 대하여 배운다.
  • 첫 세 개의 템플릿을 만들어 본다.
  • 미티어 매니저가 어떻게 작동하는 지를 배운다.
  • 정적 데이터를 처리하는 기본 프로토타입을 만들어 본다.
  • 미티어 개발에 편하게 진입할 수 있도록 우리는 바깥에서 안으로 접근하는 방식을 취할 것이다. 바꾸어 말하면, 우리는 “아무 기능도 없는” HTML/JavaScript 파일의 외형을 먼저 구축하고, 그 다음에 이를 앱의 내부 동작과 연결한다.

    이 장에서는 /client 디렉토리 내부에서 일어나는 것들에 대해서만 다룬다는 의미이다.

    새로운 파일 main.html/client 디렉토리에 생성한 다음, 아래 코드를 채워 넣는다:

    <head>
      <title>Microscope</title>
    </head>
    <body>
      <div class="container">
        <header class="navbar navbar-default" role="navigation">
          <div class="navbar-header">
            <a class="navbar-brand" href="/">Microscope</a>
          </div>
        </header>
        <div id="main" class="row-fluid">
          {{> postsList}}
        </div>
      </div>
    </body>
    
    client/main.html

    이것이 주 템플릿이 될 것이다. 보는 바와 같이, 이것은 {{> postsList}} 태그를 제외하면 모두 HTML이다. 이 때, {{> postsList}} 태그는 곧 보게 될 postsList 템플릿을 삽입할 자리이다. 이제 몇 개의 템플릿을 더 만들어보자.

    미티어 템플릿

    핵심은, 소셜 뉴스 사이트는 목록 구조의 포스트들로 이루어지는데, 템플릿을 구성하는 방식도 정확하게 일치한다.

    /client 디렉토리 하위에 /templates 디렉토리를 만들자. 이곳에 모든 템플릿을 두는데, 깔끔하게 /templates 디렉토리 하위에 /posts 디렉토리를 만들고 post 관련 템플릿들을 넣을 것이다.

    파일 찾기

    미티어는 파일을 잘 찾는다. /client 디렉토리의 어디에 넣든지 미티어는 이를 찾아서 컴파일한다. 이것은 Javascript나 CSS 파일 경로를 직접 입력할 필요가 없다는 것을 의미한다.

    이것은 동일한 디렉토리에 모든 파일을 넣을 수도 있고, 하나의 파일에 모든 코드를 넣을 수도 있다는 의미이기도 하다. 그러나 미티어가 모든 것을 컴파일하여 하나의 최적화된 파일로 만드니, 전체를 잘 구조화하고 보기 좋은 파일 구조를 사용하기 바란다.

    마침내 두 번째 템플릿을 만들 준비가 되었다. client/templates/posts 디렉토리에 posts_list.html 파일을 만든다:

    <template name="postsList">
      <div class="posts">
        {{#each posts}}
          {{> postItem}}
        {{/each}}
      </div>
    </template>
    
    client/templates/posts/posts_list.html

    그리고 post_item.html도 만든다:

    <template name="postItem">
      <div class="post">
        <div class="post-content">
          <h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
        </div>
      </div>
    </template>
    
    client/templates/posts/post_item.html

    템플릿 엘리먼트의 속성인 name="postsList"에 주목하라. 이것은 미티어가 템플릿의 위치를 추적하는 데 사용된다 (실제 파일의 이름은 상관없다).

    이제 미티어의 템플릿 시스템인 Spacebars를 소개할 차례다. Spacebars는 단순 HTML에 세 가지가 추가된 형태이다: (“partials”이라고 부르기도 하는) inclusions, expressions, 그리고 block helpers가 있다.

    Inclusions{{> templateName}}의 문법을 사용하는 데, 미티어에게 해당 위치에 같은 이름의 템플릿으로 대체하라는 의미이다(예제의 경우, postItem).

    Expressions{{title}}와 같이 현재 객체의 속성값이나, 또는 현재 템플릿 매니저(나중에 더 자세하게 다룬다)에 정의된 템플릿 헬퍼의 리턴 값을 지정한다.

    마지막으로, block helpers는 템플릿의 흐름을 제어하는 특별한 태그로, {{#each}}…{{/each}} 또는 {{#if}}…{{/if}} 같은 것들이 있다.

    더 깊이있게

    Spacebars에 대하여 더 상세한 공부를 원하면 Spacebars 문서를 참조하기 바란다.

    이 정도만 알면, 여기서 다루는 내용은 이해할 수 있다.

    먼저, postsList 템플릿에서는 posts 객체를 {{#each}}…{{/each}} 블록 헬퍼로 반복하고 있다. 그리고 반복할 때마다 postItem 템플릿을 삽입한다.

    posts 객체는 어디서 왔을까? 좋은 질문이다. 이것은 실제로는 템플릿 헬퍼로서, 동적으로 값을 지정하는 수단으로 생각하면 된다.

    postItem 템플릿은 바로 알 수 있다. 이것은 세 가지 expression만을 사용한다: {{url}}{{title}}은 둘 다 도큐먼트의 속성값을 리턴한다. 그리고 {{domain}}은 템플릿 헬퍼를 호출한다.

    템플릿 헬퍼

    지금까지 우리는 Spacebars에 대하여 알아보았는데, 이것은 이것은 몇 개의 태그를 가진 HTML과 다를 게 없다. PHP(또는 Javascript를 포함하는 정규 HTML페이지)와 같은 다른 언어와는 달리, 미티어에서는 템플릿과 그 로직이 분리되어 있으며, 템플릿 자체는 아무 일도 하지 않는다.

    템플릿이 제 기능을 하려면 헬퍼가 있어야 한다. 헬퍼는 요리사에 비유할 수 있는데 요리사는 원재료(데이터)를 가져와서, 이를 가공하고, 완성된 요리가 담긴 접시를 웨이터(템플릿)에게 전달하고 웨이터는 그것을 고객에게 제공한다.

    지금까지 Spacebars로 작업을 해 왔는데, 이것은 몇 개의 태그를 가진 HTML과 다를 게 없다. PHP(또는 Javascript를 포함하는 정규 HTML페이지)와 같은 다른 언어와는 달리, 미티어에서는 템플릿과 그 로직이 분리되어 있으며, 템플릿 자체는 아무 일도 하지 않는다.

    바꾸어 말하면, 템플릿의 역할이 변수의 값을 보여주거나, 루프를 도는 것이라면, 헬퍼는 실제로 각 변수에 값을 들고 날라 지정하는 일을 한다.

    콘트롤러?

    모든 템플릿 헬퍼들을 포함하는 파일을 일종의 콘트롤러라고 생각할 수도 있다. 하지만, 이것은 혼동을 줄 수도 있다. 왜냐면 콘트롤러란 (적어도 MVC 관점에서는) 일반적으로는 다소 다른 역할을 하기 때문이다.

    그러므로 우리는 용어 정의를 유보하고, 템플릿의 JavaScript 코드에 대하여 이야기할 때, 단순하게 “그 템플릿의 헬퍼“ 또는 “그 템플릿의 로직”이라 부르기로 했다.

    일을 쉽게 하기위해, 우리는 템플릿 이름을 따라서 헬퍼 이름을 짓고 확장자를 .js로 하는 관례를 채택할 것이다. 이에 따라 /client/templates/posts 디렉토리에 posts_list.js를 만들어 첫 헬퍼를 작성해보자:

    var postsData = [
      {
        title: 'Introducing Telescope',
        url: 'http://sachagreif.com/introducing-telescope/'
      },
      {
        title: 'Meteor',
        url: 'http://meteor.com'
      },
      {
        title: 'The Meteor Book',
        url: 'http://themeteorbook.com'
      }
    ];
    Template.postsList.helpers({
      posts: postsData
    });
    
    client/templates/posts/posts_list.js

    코드가 올바로 작성되었다면 다음과 같는 모습을 브라우저에서 볼 수 있을 것이다:

    정적 데이터로 작성한 첫 템플릿
    정적 데이터로 작성한 첫 템플릿

    Commit 3-1

    기본적인 posts 목록 템플릿과 정적 데이터를 추가했다.

    우리는 여기서 두 가지 작업을 한다. 첫째, postsData 배열에 초기 데이터를 지정했다. 이 데이터는 보통은 데이터베이스에서 오지만, 그 방법을 아직 다루지 않았기 때문에(다음 장까지 기다려라) 정적 데이터로 “임시 처리"하고 있다.

    둘째, 우리는 Template.postsList.helpers() 함수를 사용하여 posts라는 이름의 템플릿 헬퍼를 정의한다. 이 헬퍼는 단순히 postsData 배열을 리턴한다.

    기억하겠지만, 우리는 postsList 템플릿에서 posts라는 이름의 헬퍼를 사용하고 있다:

    <template name="postsList">
      <div class="posts">
        {{#each posts}}
          {{> postItem}}
        {{/each}}
      </div>
    </template>
    
    client/templates/posts/posts_list.html

    posts 헬퍼를 정의함으로써 템플릿을 사용할 수 있다. 따라서 템플릿은 postsData 배열을 반복처리하고, 그 안에 들어있는 각 배열 객체를 postItem 템플릿으로 보낸다.

    Commit 3-1

    기본적인 posts 목록 템플릿과 정적 데이터를 추가했다.

    domain 헬퍼

    유사한 방법으로, 이제 우리는 post_item.js를 만들어 postItem 템플릿의 로직을 처리한다:

    Template.postItem.helpers({
      domain: function() {
        var a = document.createElement('a');
        a.href = this.url;
        return a.hostname;
      }
    });
    
    client/templates/posts/post_item.js

    이 경우 domain 헬퍼의 값은 배열이 아니고, 익명 함수이다. 이러한 패턴은 매우 흔히 나타난다. 그리고 이전의 단순화된 더미 데이터 예제에 비교해서 훨씬 일반적이다 (그리고 더 유용하다).

    Displaying domains for each links.
    Displaying domains for each links.

    domain 헬퍼는 URL을 가져와서 약간의 JavaScript 마술을 통해서 그 URL의 도메인 값을 리턴한다. 그런데, 이 URL은 처음에 어디서 왔을까?

    이 질문의 답을 알려면 posts_list.html 템플릿으로 돌아가야 한다. {{#each}} 블록 헬퍼는 그 배열을 반복할 뿐 아니라 이 블럭 내부의 this의 값을 그 반복되는 객체에 지정한다.

    이것은 {{#each}} 태그 내부에서 각 post는 순차적으로 this로 지정되며, 그 안에 포함된 템플릿의 매니저인 (post_item.js) 내부에서 내내 사용할 수 있다는 의미이다.

    이제 this.url이 어떻게 현재 post의 URL을 리턴하는 지 알았다. 또한, post_item.html 템플릿 내부에서 {{title}}{{url}}을 사용하면, 미티어가 이것이 this.titlethis.url을 의미한다는 것을 인지하여 그 올바른 값을 리턴한다.

    Commit 3-2

    `postItem`의 `domain` 헬퍼 설정.

    JavaScript 마술

    이것이 미티어에 특정된 것은 아니지만, 여기서 위의 "JavaScript 마술"에 대하여 설명하고 넘어간다. 먼저 빈 anchor (a) HTML 엘리먼트를 생성하고 이를 메모리에 저장한다.

    그리고 그 href 속성을 현재 post의 URL로 지정한다(우리가 본 바와 같이, 헬퍼에서 this는 현재 작동중인 객체이다).

    마지막으로, a 엘리먼트의 hostname 속성을 사용하여 URL에서 도메인 이름만을 추출한다.

    올바르게 따라왔다면, 브라우저에서 목록을 볼 수 있을 것이다. 이 목록은 정적인 데이터일뿐, 아직은 미티어의 실시간 기능을 이용한 것은 아니다. 다음 장에서 이를 바꾸는 방법을 보게 될 것이다!

    Hot Code Reload

    독자는 파일이 변경될 때마다 브라우저 창을 수동으로 새로고침할 필요가 없었다는 사실을 눈치챘을 지 모르겠다.

    이것은 미티어가 프로젝트 디렉토리에 있는 모든 파일을 추적하기 때문이다. 그리고 그 파일들 중에 하나라도 변경이 감지되면 브라우저를 자동으로 새로고침한다.

    미티어의 hot code reload는 매우 똑똑해서 두 새로고침 사이의 애플리케이션의 상태도 유지한다!