: Mustache를 기반으로 구현한 간단한 templating language 이다.
핸들바 표현식은 {{ some contents }} 이다.
템플릿이 실행되면 이러한 표현식은 입력개체의 값으로 대체된다.
표현식에 사용되는 중괄호 {}가 콧수염(Mustache)을 닮았다고 하여 콧수염괄호라고도 한다.
장점
• Java, Django, Ruby, PHP, Scala 에서 사용 할 수 있다.
• 문법이 깔끔하다.
• Logic 과 Markup 을 깔끔하게 분리 할 수 있다. {{ }} 안에 있는 것이 Logic 이다.
• partials 를 지원한다.
• 컴파일 단계에서 코드가 해석이 되므로, 클라이언트에서 따로 코드를 해석하는 단계가 필요없다. 그러므로 로딩이 빠르다.
• HTML 오픈 소스를 복사/붙여넣기 가 가능하다. (pug는 불가능, pug 형식에 맞게 다시 작성해야 함)
단점
• auto-complete, syntax highlighting 등 과 같은 기능을 에디터에서 제공해 주지 않는다.
• 크게 단점이라고 느껴지는 점이 없어 보인다.
• {{title}} 과 같은 형태로 표현된다.
-- 현재 문맥에서 title이라는 속성을 찾아서 대체하는 의미
• {{section.title}} 과 같은 형태로 dot(.)로 분리된 경로탐색도 가능하다.
--현재 문맥에서 section을 찾고, title속성을 찾아 대체하는 의미
• {{section/title}} 과 같이 / 문법을 사용할 수 있다. 식별자는 아래 열거된UniCode를 제외하고 모두 사용가능하다.
-- Whitespace ! " # % & ' ( ) * + , . / ; < = > @ [ \ ] ^ ` { | } ~
• 배열 접근 표현식을 통해 Property에 접근할 수 있다.
1 2 3 4 5 6 | {{#each section.[3].titles}} <h1>{{subject}}</h1> <div> {{body}} </div> {{/each}} | cs |
• {{! }}블록으로 주석을 추가할 수 있다.
• {{log}}블록으로 템플릿 출력시 로깅할 수 있다.
• {{{--}}} 핸들바가 값을 이스케이프하지 않길 원할때 사용한다.
여러페이지에서 똑같은 구성요소를 재사용할때 사용하는 기능
(웹사이트의 header나 footer는 여러페이지에서 동일하게 사용되므로 이를 Partial로 만들어관리하면 효율적이다.)
• Basic Partials
partial 사용문법 : Handlebars.registerPartial();
• Dynamic Partials
Partial를 동적으로 생성하거나 변경하는 것
{{> (whichPartial)}}
동적 콘텐츠 로딩
: 사용자에 따라 또는 특정조건에 따라 다른내용을 보여줘야할때 사용 (ex. 로그인/비로그인 사용자 메뉴)
템플릿 구성요소의 재사용
: 공통부분을 Partial로 만들고, 필요한 곳에서 동적으로 불러와 사용 (코드중복 ↓, 유지보수 용이)
조건부 렌더링
: 특정조건에 따라 다른 Partial을 로드하거나 렌더링
(ex. 블로그 포스트의 요약부분을 짧게 보여주다가 더보기버튼을 누르면 전체포스트를 보여주는 기능 구현할때 유용)
• Partial Contexts
-
• Partial Parameters
사용자 정의데이터는 매개변수를 통해서 partial에 전달될 수 있다.
//①번 예시
//Template
{{> myPartial parameter = favoriteNumber}}
//{{>myPartial}}
The result is {{parameter}}
//Input
{favoriteNumber : 123}
//Output
The result is 123
//②번 예시
//Template
{{#each people}}
{{> myPartial prefix=../prefix firstname=firstname lastname=lastname}}.
{{/each}}
//{{>myPartial}}
{{prefix}},{{firstname}} {{lastname}}
//Input
{
people : [
{
firstname: "Nills",
lastname : "Knappmeier"
},
{
firstname: "Yehuda",
lastname: "Katz",
},
],
prefix : "Hello",
}
//Output
Hello, Nils Knappmeier.
Hello, Yehuda Katz.
• Partial Blocks
동적으로 변경될 수 있는 콘텐츠 블록을 삽입할 수 있도록하는 기능
Partial의 구조는 고정하면서도 내부 콘텐츠는 유연하게 변경할 수 있음
{{> @partial-block }}를 사용하여 콘텐츠 블록을 삽입할 위치를 지정한다.<div class="layout">
<header>
<h1>{{title}}</h1>
</header>
<main>
{{> @partial-block }}
</main>
<footer>
ⓒ 2024
</footer>
</div>
{{#> layout title="My Page Title"}}
<p>This is the main content of the page.</p>
{{/layout}}
• Inline Partials
템플릿 파일내에서 직접 Partial을 정의하고 사용하는 방법
별도의 파일로 Partial을 분리하는 대신, 하나의 템플릿 파일내에서 Partial을 정의하고, 그자리에서 바로 사용할 수 있게 해준다.
{{#*inline "partialName"}}...{{/inline}}문법을 사용하여 Partial을 정의할 수 있다.<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Inline Partials Example</title>
</head>
<body>
{{#*inline "userProfile"}}
<div class="user-profile">
<h2>{{name}}</h2>
<p>Age: {{age}}</p>
</div>
{{/inline}}
<h1>User Profiles</h1>
{{> userProfile name="John Doe" age=30 }}
{{> userProfile name="Jane Doe" age=25 }}
</body>
</html>
//호출결과
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Inline Partials Example</title>
</head>
<body>
<h1>User Profiles</h1>
<div class="user-profile">
<h2>John Doe</h2>
<p>Age: 30</p>
</div>
<div class="user-profile">
<h2>Jane Doe</h2>
<p>Age: 25</p>
</div>
</body>
</html>
Helper는 기본적으로 다른 개발언어의 함수라고 볼 수 있다.
• Basic Blocks
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{#noop}}{{body}}{{/noop}}
</div>
</div>
Handlebars.registerHelper("noop", function(options) {
return options.fn(this);
});
//결과
{{./noop}}
registerHelper - 사용자정의 헬퍼를 등록하는 함수
noop ("no operation"의 줄임말) - 아무작업도 수행하지 않는 헬퍼 {{#noop}}
function(options) - 헬퍼함수는 하나의 매개변수 ('options')를 받음
options - 다양한 속성과 메서드가 포함되어 있음 (주로, 'fn','inverse'가 사용됨
options.fn(this) - 여기서 this는 현재 context
✔️ options.fn과 options.inverse의 차이
Block Helper를 정의할때 사용하는 두가지 중요한 메서드
options.fn - Block Helper내부의 기본 콘텐츠를 렌더링하는 함수
options.inverse - Block Helper가 '{{else}}'블록을 포함할때 사용됨
'option.inverse'는 Block Helper내부의 '{{else}}'부분을 렌더링한다.
//예제
Handlebars.registerHelper("check",function(condition,option){
if(condition){
return options.fn(this);
}else{
return options.inverse(this);
}
});
//심화 예제
Handlebars.registerHelper("list",function(items,option){
Const itemAsHtml = items.map(item=> "<li>" + options.fn(item) + "<li>");
return "<ul>\n" + itemAsHtml.join("\n") + "\n</ul>";
});
Handlebars.registerHelper("list",function(items,option)
"list" - 첫번째 인수
function(items,option) - 두번째 인수 (헬퍼함수)
items,option - 두개의 매개변수
Const itemAsHtml = items.map(item=> "<li>" + options.fn(item) + "<li>");
items - 헬퍼가 호출될때 전달되는 배열
map - 배열의 각 요소에 대해 주어진 함수를 호출하고, 그결과를 새로운 배열로 반환
options.fn(item) - 현재 item을 context로 하여 Block Helpers내부의 템플릿을 렌더링한다.
map변환후
"< li>Item1</ li>","< li>Item2</ li>","< li>Item3</ li>"
return "<ul>\n" + itemAsHtml.join("\n") + "\n</ul>"
\n - 줄바꿈요소
.join() - join메서드, 배열의 모든요소를 하나의 문자열로 결합하고 각요소 사이에 줄바꿈문자를 삽입하는 부분
itemAsHtml.join("\n") - 사용하는 이유
① 각 <li>요소를 줄바꿈 문자 (\n)로 결합하여 HTML코드가 더 읽기 쉽게 하기 위함.
② HTML이 여러줄로 깔끔하게 정렬되어 유지보수와 디버깅에 용이
join사용후
"< li>Item1</ li>\n< li>Item2</ li>\n< li>Item3</ li>"
최종 HTML 결과
<ul> <li>Item1</li> <li>Item2</li> <li>Item3</li> </ul>
• Basic Block Variation
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{#noop}}{{body}}{{/noop}}
</div>
</div>
Handlebars.registerHelper("bold", function (options) {
return new Handlebars.SafeString('<div class="mybold">' + options.fn(this) + "</div>");
});
• The with helper
<div class="entry">
<h1>{{title}}</h1>
{{#with story}}
<div class="intro">{{{intro}}}</div>
<div class="body">{{{body}}}</div>
{{/with}}
</div>
{
title: "First Post",
story: {
intro: "Before the jump",
body: "After the jump"
}
}
Handlebars.registerHelper("with", function(context, options) {
return options.fn(context);
});
with헬퍼 사용부분 : {{#with story}} ... {{/with}}
//출력값
<div class="entry">
<h1>First Post</h1>
<div class="intro">Before the jump</div>
<div class="body">After the jump</div>
</div>
• Simple Iterators
-
• Conditionals
If / Unless
if
조건문이 false일때는 else블록도우미에 일반기능을 제공하여 문제처리한다.
또다른 조건문이 생겼을때 (후속 helper) else if도 사용가능
• Hash Arguments
-
• Block Parameters
{{#each users as |user userId|}}
Id: {{userId}} Name: {{user.name}}
{{/each}}
user = userId
• Raw Blocks
처리되지 않은 콧수염블록을 처리해야하는 템플릿에 Raw Blocks을 사용할 수 있다.
{{{{raw-loud}}}}
{{bar}}
{{{{/raw-loud}}}}
Handlebars.registerHelper('raw-loud', function(options) {
return options.fn().toUpperCase()
});
//출력값
{{BAR}}
블록내부의 콘텐츠를 대문자로 변환하여 렌더링해라
• #If
조건부로 렌더링할때 사용한다.
return값을 false, undefined, null, "", 0, [] 로 반환하는 경우, 블록은은 렌더링하지 않는다.
**①번 (if가 참일 경우)**
<div class="entry">
{{#if author}}
<h1>{{firstName}} {{lastName}}</h1>
{{/if}}
</div>
//input
{
author: true,
firstName: "Yehuda",
lastName: "Katz",
}
//output
<div class="entry">
<h1>Yehuda Katz</h1>
</div>
if값이 true여서 output을 출력한다.
**②번 (if가 거짓일 경우(비었을 경우))**
input값이 비었으면 (null)
//output
<div class="entry"></div>
👉블록표현식으로 사용할 때 표현식이 falsy값을 반환하는 경우
else로 표현할 수 있음
<div class="entry">
{{#if author}}
<h1>{{firstName}} {{lastName}}</h1>
{{else}}
<h1>Unknown Author</h1>
{{/if}}
</div>
//input
{
author: false,
firstName: "Yehuda",
lastName: "Katz",
}
//output
<div class="entry">
<h1>Unknown Author</h1>
</div>
**includeZero
includeZero=true옵션은 조건을 비어있지 않는 것으로 처리하도록 설정할 수 있음
(이는 0 양수 or 음수로 처리여부를 판별)
{{#if 0 includeZero=true}}
<h1>Does render</h1>
{{/if}}
** Sub-Expressions (하위표현식)
템플릿에 사용자 지정논리를 추가하기 위해 제안된 방법
#if는 false를 반환하므로 적합하지 않을 수 있음
👉 undefined를 사용
//예제
//template
{{#if (isdefined value1)}}true{{else}}false{{/if}}
{{#if (isdefined value2)}}true{{else}}false{{/if}}
//script
Handlebars.registerHelper('isdefined', function (value) {
return value !== undefined;
});
//input
{ value1: {} }
//output
true
false
input에 value1에 대한 값만 구했으니, true를 반환하고,
value2값은 없으니 false를 반환한다.
• #Unless
if의 반대로 unless를 사용한다.
표현식이 거짓값을 반환하면 해당블록이 렌더링된다.
<div class="entry">
{{#unless license}}
<h3 class="warning">WARNING: This entry does not have a license!</h3>
{{/unless}}
</div>
//input
{}
//output
<div class="entry">
<h3 class="warning">WARNING: This entry does not have a license!</h3>
</div>
• #each
each헬퍼를 사용하여 목록을 반복할 수 있음
<ul class="people_list">
{{#each people}}
<li>{{this}}</li>
{{/each}}
</ul>
//input
{
people: [
"Yehuda Katz",
"Alan Johnson",
"Charles Jolley",
],
}
//output
<ul class="people_list">
<li>Yehuda Katz</li>
<li>Alan Johnson</li>
<li>Charles Jolley</li>
</ul>
else는 목록이 비어있을때 선택적으로 사용가능하다
{{#each paragraphs}}
<p>{{this}}</p>
{{else}}
<p class="empty">No content</p>
{{/each}}
//input
{} //값이비었음
//output
<p class="empty">No content</p>
**항목 반복 (현재 loop index를 참조){{@index}}
{{#each array}} {{@index}}: {{this}} {{/each}}
**객체 반복 (현재 loop index를 참조) {{@key}}
{{#each object}} {{@key}}: {{this}} {{/each}}
반복의 첫번째와 마지막단계에서 배열반복은 @first / @last로 표시
중첩된 each블록은 반복변수을 통해 기반경로로 접근할수있다.
(ex. 상위index에 접근하려면, {{@../index}}로 사용)
• #with
with helper는 template-part의 평가 context를 변경할 수 있음
{{#with person}}
{{firstname}} {{lastname}}
{{/with}}
//input
{
person: {
firstname: "Yehuda",
lastname: "Katz",
},
}
//output
Yehuda Katz
with는 현재블록에 참조를 정의하기 위해 매개변수와 함께 사용할수 있음
//예제
{{#with city as | city |}}
{{#with city.location as | loc |}}
{{city.name}}: {{loc.north}} {{loc.east}}
{{/with}}
{{/with}}
//input
{
city: {
name: "San Francisco",
summary: "San Francisco is the <b>cultural center</b> of <b>Northern California</b>",
location: {
north: "37.73,",
east: -122.44,
},
population: 883305,
},
}
//output
San Francisco: 37.73, -122.44
../ - 템플릿이 더 명확한 코드를 제공할 가능성이 있다.
{{else}} - 전달값이 비어있는 경우 선택적 제공가능
{{#with city}}
{{city.name}} (not shown because there is no city)
{{else}}
No city found
{{/with}}
//input
{
person: {
firstname: "Yehuda",
lastname: "Katz",
},
}
//output
No city found
city name이 없기때문에 false를 반환하는데, else가 있어 else값을 반환한다.
• lookup
동적매개변수 확인을 도와준다 (배열 인덱스의 값을 확인에 유용)
{{#each people}}
{{.}} lives in {{lookup ../cities @index}}
{{/each}}
//input
{
people: ["Nils", "Yehuda"],
cities: [
"Darmstadt",
"San Francisco",
],
}
//output
Nils lives in Darmstadt
Yehuda lives in San Francisco
또한, 입력의 데이터를 기반으로 객체의 속성을 조회하는데 사용가능
as || - ||을 별칭으로 사용한다.
//고급예제
{{#each persons as | person |}}
{{name}} lives in {{#with (lookup ../cities [resident-in])~}}
{{name}} ({{country}})
{{/with}}
{{/each}}
//input
{
persons: [
{
name: "Nils",
"resident-in": "darmstadt",
},
{
name: "Yehuda",
"resident-in": "san-francisco",
},
],
cities: {
darmstadt: {
name: "Darmstadt",
country: "Germany",
},
"san-francisco": {
name: "San Francisco",
country: "USA",
},
},
}
//output
Nils lives in Darmstadt (Germany)
Yehuda lives in San Francisco (USA)
{{#each persons as | person |}} - persons를 person으로 별칭한다.
{{name}} - 현재 person 객체의 name 속성을 출력
{{#with (lookup ../cities [resident-in])~}} -
▸ lookup헬퍼를 사용하여 cities객체에서 현재 person의 resident-in 속성 값에 해당하는 도시 객체를 찾음
▸ ../cities는 상위 context의 cities객체를 참조
▸ [resident-in] - 현재 person 객체의 resident-in 속성 값을 동적으로 사용하여 cities객체에서 해당도시를 조회한다
▸ ~ 문자는 공백 제거를 의미
• log
log를 사용하면 템플릿의 context상태를 기록할수 있음
재정의 - Handlebars.logger.log
//지원되는 값 (debug,info,warn,error,info logging is the default)
{{log "debug logging" level="debug"}}
{{log "info logging" level="info"}}
{{log "info logging is the default"}}
{{log "logging a warning" level="warn"}}
{{log "logging an error" level="error"}}
조건부 - Handlebars.logger.level 기본값 - info
가장 기본적인 바인딩 구조
<script id="entry-template" type="text/x-handlebars-template">
<table>
<thead>
<th>이름</th>
<th>아이디</th>
<th>메일주소</th>
</thead>
<tbody>
{{#users}}
<tr>
<td>{{name}}</td>
<td>{{id}}</td>
<td><a href="mailto:{{email}}">{{email}}</a></td>
</tr>
{{/users}}
</tbody>
</table>
</script>
<script>태그의 type속성을 살펴보면 text/x-handlebars-template을 통해 핸들바 템플릿을 사용한다는 것을 알 수 있다.
더불어, {{#users}}와 {{/users}}로 감싸진 부분은 users라는 배열의 길이만큼 반복된다. {{name}}처럼 내부에 괄호로 감싸진 부분은 배열 요소 값으로 바뀌는 부분이다.
앞서 말했듯이, Handlebars는 자바스크립트함수로 컴파일된다. 예제의 js파일을 살펴보자.
//핸들바 템플릿 가져오기
var source = $("#entry-template").html();
//핸들바 템플릿 컴파일
var template = Handlebars.compile(source);
//핸들바 템플릿에 바인딩할 데이터
var data = {
users: [
{ name: "홍길동1", id: "aaa1", email: "aaa1@gmail.com" },
{ name: "홍길동2", id: "aaa2", email: "aaa2@gmail.com" },
{ name: "홍길동3", id: "aaa3", email: "aaa3@gmail.com" },
{ name: "홍길동4", id: "aaa4", email: "aaa4@gmail.com" },
{ name: "홍길동5", id: "aaa5", email: "aaa5@gmail.com" }
]
};
//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성
var html = template(data);
//생성된 HTML을 DOM에 주입
$('body').append(html);
id를 통해 핸들바 템플릿을 가져오고, 컴파일이 이루어진다. 변수 html에 파라미터로 데이터를 넣어주고 JQuery로 body끝에 html을 추가하였다.
참조
https://handlebarsjs.com/
https://velog.io/@somin_0/Handlebars
https://programmingsummaries.tistory.com/381
https://ijbgo.tistory.com/7
https://velog.io/@hanblueblue/프로젝트1-3.-Handlebars-이용해-프론트-템플릿-만들기
https://velog.io/@parkoon/실무에서-Handlebars-사용하기-feat-express
https://enai.tistory.com/22
+thanks to chatGPT