HTML 서비스: 템플릿 HTML

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

Apps Script 코드와 HTML을 함께 사용하면 최소한의 노력으로 동적 페이지를 생성할 수 있습니다. PHP와 ASP, JSP와 같이 코드와 HTML을 혼합하는 템플릿 언어를 사용했다면 문법이 익숙할 것입니다.

스크립트

Apps Script 템플릿은 Scriptlet이라는 세 가지 특수 태그를 포함할 수 있습니다. 스크립트 세트 내에서 일반 Apps Script 파일에 사용할 수 있는 모든 코드를 작성할 수 있습니다. 스크립트릿은 다른 코드 파일에 정의된 함수를 호출하거나 전역 변수를 참조하거나 Apps Script API를 사용할 수 있습니다. 스크립트 파일 내에 함수와 변수를 정의할 수도 있지만, 코드 파일이나 다른 템플릿에 정의된 함수에서는 함수를 호출할 수 없습니다.

아래 예를 스크립트 편집기에 붙여넣으면 <?= ... ?> 태그의 콘텐츠 (인쇄 스크립트)가 기울임꼴로 표시됩니다. 기울임꼴 코드는 페이지가 사용자에게 제공되기 전에 서버에서 실행됩니다. 스크립트 세트 코드는 페이지가 처리되기 전에 실행되므로 페이지당 한 번만 실행할 수 있습니다. google.script.run를 통해 호출하는 클라이언트 측 자바스크립트나 Apps Script 함수와 달리 스크립트는 페이지가 로드된 후 다시 실행할 수 없습니다.

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    Hello, World! The time is <?= new Date() ?>.
  </body>
</html>

템플릿 HTML의 doGet() 함수는 기본 HTML 만들기 및 제공의 예와 다릅니다. 여기에 표시된 함수는 HTML 파일에서 HtmlTemplate 객체를 생성한 다음 evaluate() 메서드를 호출하여 스크립트를 실행하고 템플릿을 사용자에게 제공할 수 있는 HtmlOutput 객체로 템플릿을 변환합니다.

표준 스크립트릿

<? ... ?> 문법을 사용하는 표준 스크립트릿은 명시적으로 페이지에 콘텐츠를 출력하지 않고 코드를 실행합니다. 하지만 이 예에서와 같이 스크립트릿 코드의 코드 결과는 스크립트릿 외부의 HTML 콘텐츠에 계속 영향을 줄 수 있습니다.

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <? if (true) { ?>
      <p>This will always be served!</p>
    <? } else  { ?>
      <p>This will never be served.</p>
    <? } ?>
  </body>
</html>

인쇄 스크립트릿

<?= ... ?> 구문을 사용하는 스크립트 스크립팅은 컨텍스트 이스케이프 처리를 사용하여 코드 결과를 페이지에 출력합니다.

컨텍스트 이스케이프를 사용하면 Apps Script가 HTML 속성 내, 클라이언트 측 script 태그 내 또는 다른 모든 위치에서 페이지의 출력 컨텍스트를 추적하고 이스케이프 문자를 자동으로 추가하여 교차 사이트 스크립팅 (XSS) 공격으로부터 보호합니다.

이 예에서 첫 번째 인쇄 스크립트릿은 문자열을 직접 출력합니다. 그 뒤에는 배열과 루프를 설정하는 표준 스크립트렛이 나온 후 배열의 콘텐츠를 출력하는 또 다른 인쇄 스크립트릿이 출력됩니다.

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <?= 'My favorite Google products:' ?>
    <? var data = ['Gmail', 'Docs', 'Android'];
      for (var i = 0; i < data.length; i++) { ?>
        <b><?= data[i] ?></b>
    <? } ?>
  </body>
</html>

인쇄 스크립트에서는 첫 번째 문의 값만 출력합니다. 나머지 문은 표준 스크립트릿에 포함된 것처럼 작동합니다. 예를 들어 스크립트 스크립트 <?= 'Hello, world!'; 'abc' ?>은 "Hello, world!"

스크립트 인쇄 강제 실행

구문 <?!= ... ?>를 사용하는 강제 인쇄 스크립트는 컨텍스트 이스케이프 처리를 방지한다는 점을 제외하면 스크립팅 인쇄와 유사합니다.

스크립트에서 신뢰할 수 없는 사용자 입력을 허용하는 경우 문맥 이스케이프 처리가 중요합니다. 반면에 스크립트릿의 출력에 지정된 대로 정확하게 삽입하려는 HTML이나 스크립트가 의도적으로 포함된 경우 강제 인쇄해야 합니다.

일반적으로 HTML 또는 자바스크립트를 변경하지 않고 인쇄해야 하는 경우를 제외하고는 스크립트릿을 강제 인쇄하는 대신 인쇄 스크릿릿을 사용하세요.

Scriptlet의 Apps Script 코드

Scriptlet은 일반 자바스크립트 실행으로 제한되지 않습니다. 다음 세 가지 기법 중 하나를 사용하여 템플릿에 Apps Script 데이터에 대한 액세스 권한을 부여할 수도 있습니다.

하지만 페이지가 사용자에게 표시되기 전에 템플릿 코드가 실행되므로 이러한 기법을 사용하면 페이지에 초기 콘텐츠만 제공할 수 있습니다. 페이지에서 Apps Script 데이터에 대화형으로 액세스하려면 google.script.run API를 대신 사용하세요.

템플릿에서 Apps Script 함수 호출

Scriptlet은 Apps Script 코드 파일 또는 라이브러리에 정의된 모든 함수를 호출할 수 있습니다. 이 예시에서는 스프레드시트에서 템플릿으로 데이터를 가져온 다음 데이터에서 HTML 테이블을 구성하는 한 가지 방법을 보여줍니다.

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

function getData() {
  return SpreadsheetApp
      .openById('1234567890abcdefghijklmnopqrstuvwxyz')
      .getActiveSheet()
      .getDataRange()
      .getValues();
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <? var data = getData(); ?>
    <table>
      <? for (var i = 0; i < data.length; i++) { ?>
        <tr>
          <? for (var j = 0; j < data[i].length; j++) { ?>
            <td><?= data[i][j] ?></td>
          <? } ?>
        </tr>
      <? } ?>
    </table>
  </body>
</html>

Apps Script API 직접 호출

또한 스크립트 스크립트에서 직접 Apps Script 코드를 사용할 수도 있습니다. 이 예시에서는 별도의 함수를 통하지 않고 템플릿 자체에 데이터를 로드하여 이전 예시와 동일한 결과를 얻습니다.

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <? var data = SpreadsheetApp
        .openById('1234567890abcdefghijklmnopqrstuvwxyz')
        .getActiveSheet()
        .getDataRange()
        .getValues(); ?>
    <table>
      <? for (var i = 0; i < data.length; i++) { ?>
        <tr>
          <? for (var j = 0; j < data[i].length; j++) { ?>
            <td><?= data[i][j] ?></td>
          <? } ?>
        </tr>
      <? } ?>
    </table>
  </body>
</html>

템플릿으로 변수 푸시

마지막으로 변수를 HtmlTemplate 객체의 속성으로 할당하여 템플릿에 푸시할 수 있습니다. 다시 한 번 이 예의 결과는 이전 예시와 동일한 결과를 얻습니다.

Code.gs

function doGet() {
  var t = HtmlService.createTemplateFromFile('Index');
  t.data = SpreadsheetApp
      .openById('1234567890abcdefghijklmnopqrstuvwxyz')
      .getActiveSheet()
      .getDataRange()
      .getValues();
  return t.evaluate();
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <table>
      <? for (var i = 0; i < data.length; i++) { ?>
        <tr>
          <? for (var j = 0; j < data[i].length; j++) { ?>
            <td><?= data[i][j] ?></td>
          <? } ?>
        </tr>
      <? } ?>
    </table>
  </body>
</html>

디버깅 템플릿

작성하는 코드는 직접 실행되지 않으므로 템플릿을 디버그하기가 어려울 수 있습니다. 대신 서버는 템플릿을 코드로 변환한 후 결과 코드를 실행합니다.

템플릿이 스크립트를 해석하는 방식이 분명하지 않은 경우 HtmlTemplate 클래스의 두 가지 디버깅 메서드를 사용하면 진행 상황을 더 잘 파악할 수 있습니다.

getCode()

getCode()은 서버가 템플릿에서 만드는 코드를 포함하는 문자열을 반환합니다. 코드를 로그한 다음 스크립트 편집기에 붙여넣으면 이를 실행하고 일반 Apps Script 코드처럼 디버그할 수 있습니다.

다음은 Google 제품 목록을 다시 표시하고 getCode() 결과를 보여주는 간단한 템플릿입니다.

Code.gs

function myFunction() {
  Logger.log(HtmlService
      .createTemplateFromFile('Index')
      .getCode());
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <?= 'My favorite Google products:' ?>
    <? var data = ['Gmail', 'Docs', 'Android'];
      for (var i = 0; i < data.length; i++) { ?>
        <b><?= data[i] ?></b>
    <? } ?>
  </body>
</html>

LOG (평가됨)

(function() { var output = HtmlService.initTemplate(); output._ =  '<!DOCTYPE html>\n';
  output._ =  '<html>\n' +
    '  <head>\n' +
    '    <base target=\"_top\">\n' +
    '  </head>\n' +
    '  <body>\n' +
    '    '; output._$ =  'My favorite Google products:' ;
  output._ =  '    ';  var data = ['Gmail', 'Docs', 'Android'];
        for (var i = 0; i < data.length; i++) { ;
  output._ =  '        <b>'; output._$ =  data[i] ; output._ =  '</b>\n';
  output._ =  '    ';  } ;
  output._ =  '  </body>\n';
  output._ =  '</html>';
  /* End of user code */
  return output.$out.append('');
})();

getCodeWith댓글s()

getCodeWithComments()getCode()와 유사하지만 평가된 코드를 원래 템플릿과 나란히 표시되는 주석으로 반환합니다.

평가된 코드 안내

평가된 코드 샘플에서 가장 먼저 보게 될 것은 HtmlService.initTemplate() 메서드로 생성된 암시적 output 객체입니다. 이 메서드는 문서화되지 않았으므로 템플릿 자체에서만 사용해야 합니다. output은 보통 이름이 2가지인 __$가 포함된 특수한 HtmlOutput 객체로 append()appendUntrusted()의 약식입니다.

output에는 이러한 특수 속성이 없는 일반 HtmlOutput 객체를 참조하는 특수 속성 $out가 하나 더 있습니다. 템플릿은 코드 끝에 이 일반 객체를 반환합니다.

이제 이 문법을 이해했으므로 나머지 코드는 쉽게 따라할 수 있습니다. 스크립트 태그 외부에 있는 HTML 콘텐츠(예: b 태그)는 output._ =를 사용하여(컨텍스트 이스케이프 처리 없이) 추가되며, 스크립트릿은 스크립트릿 유형에 따라 컨텍스트 이스케이프 처리 여부와 관계없이 자바스크립트로 추가됩니다.

평가된 코드는 템플릿의 줄 번호를 유지합니다. 평가된 코드를 실행하는 중에 오류가 발생하면 줄이 템플릿에 있는 해당 콘텐츠에 해당합니다.

댓글 계층 구조

평가된 코드는 행 번호를 유지하므로 스크립트릿의 주석에 다른 스크립트릿과 HTML 코드도 주석 처리될 수 있습니다. 다음 예는 댓글의 놀라운 효과를 보여줍니다.

<? var x; // a comment ?> This sentence won't print because a comment begins inside a scriptlet on the same line.

<? var y; // ?> <?= "This sentence won't print because a comment begins inside a scriptlet on the same line.";
output.append("This sentence will print because it's on the next line, even though it's in the same scriptlet.”) ?>

<? doSomething(); /* ?>
This entire block is commented out,
even if you add a */ in the HTML
or in a <script> */ </script> tag,
<? until you end the comment inside a scriptlet. */ ?>