「JavaScript library」の版間の差分

提供:senooken JP Wiki
(JavaScriptからFrameworkを分離。)
 
 
(同じ利用者による、間の12版が非表示)
1行目: 1行目:
== jQuery ==
[[Category:JavaScript]]
== Tool ==
=== Grunt ===
 
==== About ====
JavaScriptのタスクランナー。ビルドやファイルの整形、変換などを行ってくれる。
 
gruntの他にgulpというのもある。
 
[https://gruntjs.com/ Grunt: The JavaScript Task Runner]
 
===== grunt vs. gulp =====
[https://ics.media/entry/3290/ 絶対つまずかないGulp 5入門 - インストールとSassを使うまでの手順 - ICS MEDIA]
 
設定ファイルが違う。
 
* grunt: JSONで同期。
* gulp: JavaScriptで非同期。
 
==== Gruntfile ====
Gruntfile.jsかGruntfile.coffeがタスクの定義ファイル。
 
以下のコマンドでgruntで実行可能なタスク一覧が表示される。
grunt --help
単にgruntとだけ実行すると、defaultのタスクが実行される。基本はサブコマンドを指定する。
 
=== Node.js ===
 
==== Install ====
[https://nodejs.org/en/download/package-manager Node.js — Download Node.js®]
 
ここでプラットフォーム別のインストール方法がある。
 
Linuxの場合、以下。
# installs nvm (Node Version Manager)
curl -o- <nowiki>https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh</nowiki> | bash
# download and install Node.js (you may need to restart the terminal)
nvm install 22
# verifies the right Node.js version is in the environment
node -v # should print `v22.12.0`
# verifies the right npm version is in the environment
npm -v # should print `10.9.0`
最初の行を実行してインストールしたら~/.bashrcに以下の内容が追記される。即座にインストールを反映したければ現在の端末でも実行する。
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion
 
== Framework ==
 
=== Ember.js ===
 
* [https://emberjs.com/ Ember.js - A framework for ambitious web developers]
* [https://engineer-style.jp/articles/9522 Ember.jsとは?具体的な活用事例や他フレームワークとの比較を紹介 | エンジニアスタイル]
 
Angular/Vue/ReactのようなJavaScriptフレームワーク。クライアントサイドのMVCフレームワーク。サーバーからデータをもらって、それをJSで描画するタイプ。
 
2011年に誕生。
 
==== Getting Started ====
 
* [https://dev.classmethod.jp/articles/hello-emberjs/ Ember.js はじめました – Ember.js入門(1) | DevelopersIO]
 
重要な基本概念がある。それらを簡単に整理。
 
* Template: MVCのV担当。表示用HTMLのテンプレートhandlebars.jsを使っている。Modelの変数を使って表示する。
* Component: Templateを機能単位で固めたもの。
* Router: ルーティング。リクエストURLに対する処理を行う。このリクエストに対して、Modelからデータを取得してVで表示する。MVCのC相当。
* Model: 永続化データの格納オブジェクト。サーバーからJSON APIで取得した結果を変数 (Modelオブジェクト) に格納する。
* Route: どのモデルを表示するかをTemplateに伝えるオブジェクト。
* Controller:
 
基本的な構造。<syntaxhighlight lang="html">
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World - Ember.js</title>
</head>
<body>
<script type="text/x-handlebars">
<h1>Hello World</h1>
<p>Hello, {{name}}</p>
</script>
<script type="text/javascript" src="js/libs/jquery-1.9.1.js"></script>
<script type="text/javascript" src="js/libs/handlebars-1.0.0.js"></script>
<script type="text/javascript" src="js/libs/ember-1.0.0-rc.7.js"></script>
<script type="text/javascript">
window.App = Ember.Application.create();
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return {name: 'Ember.js'};
}
});
</script>
</body>
</html>
</syntaxhighlight>
 
===== Obtaining Ember.js and Dependencies =====
[https://guides.emberjs.com/v1.10.0/getting-started/obtaining-emberjs-and-dependencies/ How To Use The Guides - Getting Started - Ember Guides]
  <script src="js/libs/jquery-1.11.2.min.js"></script>
  <script src="js/libs/handlebars-v1.3.0.js"></script>
  <script src="js/libs/ember.js"></script>
  <script src="js/libs/ember-data.js"></script>
</body>
 
==== Concepts ====
 
===== Naming Conventions =====
[https://guides.emberjs.com/v1.10.0/concepts/naming-conventions/ Naming Conventions - Concepts - Ember Guides]
 
いくつかの命名規則で、ルーティングが決まる。
 
====== The Application ======
アプリの起動時に、以下のオブジェクトを探す。
 
* App.ApplicationRoute
* App.ApplicationController
* the application template
 
これがデフォルトのルートみたいなもの。
 
====== The Index Route ======
Emberは自動的に、/のパスをindexのとみなす。
 
* <code>App.IndexRoute</code>
* <code>App.IndexController</code>
* the <code>index</code> template
 
上記になる。
 
==== Templates ====
[https://guides.emberjs.com/v1.10.0/templates/ How To Use The Guides - Getting Started - Ember Guides]
 
===== Handlebars Basics =====
[https://guides.emberjs.com/v1.10.0/templates/handlebars-basics/ How To Use The Guides - Getting Started - Ember Guides]
 
Ember.jsはHandlebarsテンプレートライブラリーを使っている。
 
HTMLのテンプレート。表示部分。
 
===== Actions =====
[https://guides.emberjs.com/v1.10.0/templates/actions/ Actions - Templates - Ember Guides]
 
====== <nowiki>{{action}}</nowiki> helper ======
アプリで、ユーザーがアプリの状態を変更するコントローラーを操作できるようにする方法が必要になる。例えば、ボタンを押したら、処理を行うような感じ。<syntaxhighlight lang="html+handlebars">
<!-- post.handlebars -->
 
<div class='intro'>
  {{intro}}
</div>
 
{{#if isExpanded}}
  <div class='body'>{{body}}</div>
  <button {{action 'contract'}}>Contract</button>
{{else}}
  <button {{action 'expand'}}>Show More...</button>
{{/if}}
 
<script>
    App.PostController = Ember.ObjectController.extend({
  // initial value
  isExpanded: false,
 
  actions: {
    expand: function() {
      this.set('isExpanded', true);
    },
 
    contract: function() {
      this.set('isExpanded', false);
    }
  }
});
</script>
</syntaxhighlight>コントローラーのactionオブジェクト内の同じ名前のメソッドを使う。
 
==== Routing ====
 
===== Defining Your Routes =====
[https://guides.emberjs.com/v1.10.0/routing/defining-your-routes/ Defining Your Routes - Routing - Ember Guides]
 
アプリケーションの開始時に、現在URLにマッチするルートのControllerを表示する。そのルーティングを定義する。
 
いくつかの方法がある。
 
まずシンプルなのがmapメソッドを使う方法。
App.Router.map(function() {
  this.route('about', { path: '/about' });
  this.route('favorites', { path: '/favs' });
});
例えば、/aboutにアクセス時に、aboutテンプレートを描画する。
 
pathを省略すると、テンプレート名をpathとみなす。
 
他に、Ember.Routeのサブクラスとして実装する方法もある。
App.IndexRoute = Ember.Route.extend({
  setupController: function(controller) {
    // Set the IndexController's `title`
    controller.set('title', 'My App');
  }
});
上記のようにすると、IndexControllerをindexテンプレートとともに使う。Routeの前の部分の小文字をベースネームとして使う。
{| class="wikitable"
!URL
!Route Name
!Controller
!Route
!Template
|-
|<code>/</code>
|<code>index</code>
|<code>IndexController</code>
|<code>IndexRoute</code>
|<code>index</code>
|-
|<code>/about</code>
|<code>about</code>
|<code>AboutController</code>
|<code>AboutRoute</code>
|<code>about</code>
|-
|<code>/favs</code>
|<code>favorites</code>
|<code>FavoritesController</code>
|<code>FavoritesRoute</code>
|<code>favorites</code>
|}
こういう対応関係。
 
====== Dynamic Segments ======
URLをモデルに変換することもできる。
App.PostsRoute = Ember.Route.extend({
  model: function() {
    return this.store.find('post');
  }
});
これで/postにアクセス時にstoreのpostオブジェクトを渡す。
 
===== Specifying a Route's Model =====
[https://guides.emberjs.com/v1.10.0/routing/specifying-a-routes-model/ Defining Your Routes - Routing - Ember Guides]
 
アプリのテンプレートはmodelを背景に持つ。テンプレートがどのモデルを持つかをmodelプロパティーのフックに定義する。
App.PhotosRoute = Ember.Route.extend({
  model: function() {
    return return {name: 'Ember.js'};
  }
});
こんな感じで定義したら、template内でnameの変数が使える感じ。たぶん、thisの省略形?
 
===== Controllers =====
[https://guides.emberjs.com/v1.10.0/controllers/ Introduction - Controllers - Ember Guides]
 
====== init ======
 
* [https://guides.emberjs.com/v1.10.0/understanding-ember/ Actions - Templates - Ember Guides]
* [https://api.emberjs.com/ember/1.0/classes/Ember.Controller/methods/init?anchor=init Ember.Controller - 1.0 - Ember API Documentation]
 
Controllerのインスタンス生成、初期化時に呼び出す処理をinitに登録できる。コンストラクター的なもの。
 
=== jQuery ===
[https://api.jquery.com/ jQuery API Documentation]
[https://api.jquery.com/ jQuery API Documentation]


=== レガシー ===
==== レガシー ====


* [https://yori-york.co.jp/news/190612_jquery jQueryはもう古い!?これから・・・ | YORIYORK|栃木県佐野市]
* [https://yori-york.co.jp/news/190612_jquery jQueryはもう古い!?これから・・・ | YORIYORK|栃木県佐野市]
16行目: 276行目:
上記2点が大きい。jQueryじゃないとできないことが大幅に減った。標準APIでこなしたほうがいいだろう。
上記2点が大きい。jQueryじゃないとできないことが大幅に減った。標準APIでこなしたほうがいいだろう。


=== ajaxのasync/awaitでの書換 ===
==== ajaxのasync/awaitでの書換 ====
[https://qiita.com/toromo/items/42195b7a4480fb363478 $.ajax(jQuery.ajax)の非同期処理をasync awaitの入れ子なし同期処理で記述する #JavaScript - Qiita]
[https://qiita.com/toromo/items/42195b7a4480fb363478 $.ajax(jQuery.ajax)の非同期処理をasync awaitの入れ子なし同期処理で記述する #JavaScript - Qiita]


ajaxを使っている関数の定義にasync。ajax関数の使用箇所にawait。これでうまくいく。
ajaxを使っている関数の定義にasync。ajax関数の使用箇所にawait。これでうまくいく。
**
**
  * フルーツ名を取得する
* フルーツ名を取得する
  *
*
  * @param {string} fruitId
* @param {string} fruitId
   */
   */
async function getFruitName(fruitId) {
  async function getFruitName(fruitId) {
  const fruitRequest = {id: fruitId}
  const fruitRequest = {id: fruitId}
  const fruitResult = await ajaxGetFruit(fruitRequest);
  const fruitResult = await ajaxGetFruit(fruitRequest);
  return fruitResult.name;
  return fruitResult.name;
}
  }
 
/**
/**
  * フルーツのajax[GET]を実施する
* フルーツのajax[GET]を実施する
  *  
*
  * @param {object} request
* @param {object} request
  * @returns
* @returns
   */
   */
function ajaxGetFruit(request) {
  function ajaxGetFruit(request) {
  return $.ajax({
  return $.ajax({
      url: '/fruit/name',
  url: '/fruit/name',
      type: "GET",
  type: "GET",
      async: true,
  async: true,
      contentType: "application/json",
  contentType: "application/json",
      data: JSON.stringify(request),
  data: JSON.stringify(request),
      dataType: "json",
  dataType: "json",
    }).then(
  }).then(
      function (result) {
  function (result) {
        // 正常終了
  // 正常終了
        resolve(result);
  resolve(result);
      },
  },
      function () {
  function () {
        // エラー
  // エラー
        reject();
  reject();
      }
  }
    )
  )
}
  }
「[https://api.jquery.com/jQuery.ajax/ jQuery.ajax() | jQuery API Documentation]」にあるように、jquery 1.5から$.ajaxの返却値はPromiseの派生クラス。そのまま返却させて良い。
  「[https://api.jquery.com/jQuery.ajax/ jQuery.ajax() | jQuery API Documentation]」にあるように、jquery 1.5から$.ajaxの返却値はPromiseの派生クラス。そのまま返却させて良い。
 
==== DataTables ====
jqueryのプラグイン。テーブル処理をうまくやってくれる。
 
===== Option =====
 
* [https://legacy.datatables.net/ref.html DataTables - Reference]
* [https://datatables.net/reference/option/ Options]
 
===== Old =====
[https://legacy.datatables.net/index-2.html DataTables (table plug-in for jQuery)]
 
v1.9以下の古いバージョンのサイトはこちらにある。
 
===== ServerSide =====
[https://datatables.net/manual/server-side Server-side processing]
 
通常はクライアント画面のみ。全データを検索したい場合、serverSideオプションが必要。ただし、このオプションはdatatablesのv1.10以上じゃない。
 
===== Pagination =====
ページ表示件数に関するオプションがいくつかある。


* pageLength/iDisplayLength: 1ページあたりの表示件数。デフォルトは10。
* lengthMenu/aLengthMenu: リストボックスの項目。デフォルトは[10, 25, 50, 100]。-1 ("All") で全表示。


== Nuxt.js ==
=== Nuxt.js ===


=== 終端スラッシュありへのリダイレクト ===
==== 終端スラッシュありへのリダイレクト ====
[https://qiita.com/too/items/1b0944f5acb2aeb4e9b9 Nuxt.jsにおける末尾スラッシュを統一する方法 #amplify - Qiita]
[https://qiita.com/too/items/1b0944f5acb2aeb4e9b9 Nuxt.jsにおける末尾スラッシュを統一する方法 #amplify - Qiita]
// middleware/trailingSlash.js
// middleware/trailingSlash.js
/**
/**
  * 終端スラッシュありのURLにリダイレクトする。
* 終端スラッシュありのURLにリダイレクトする。
  * @param {Object} Nuxt.jsのContextオブジェクト。
* @param {Object} Nuxt.jsのContextオブジェクト。
   */
   */
export default ({ route, redirect }) => {
  export default ({ route, redirect }) => {
  if (route.path.endsWith('/')) {
  if (route.path.endsWith('/')) {
    return
  return
  }
  }
  let to = route.path + '/'
  let to = route.path + '/'
  if (route.query) {
  if (route.query) {
    to +=
  to +=
      '?' +
  '?' +
      Object.entries(route.query)
  Object.entries(route.query)
        .map((e) => e.join('='))
  .map((e) => e.join('='))
        .join('&')
  .join('&')
  }
  }
  to += route.hash
  to += route.hash
  redirect(301, to)
  redirect(301, to)
}
  }


=== //へのアクセスエラー ===
==== //へのアクセスエラー ====


* <nowiki>https://github.com/vuejs/vue-router/issues/2593</nowiki>
* <nowiki>https://github.com/vuejs/vue-router/issues/2593</nowiki>
92行目: 375行目:


そのため、プラグインにして、router.beforeEachで変化前に書き換え処理を入れた。
そのため、プラグインにして、router.beforeEachで変化前に書き換え処理を入れた。
// plugins/redirect.js
// plugins/redirect.js
export default ({ app }) => {
export default ({ app }) => {
  app.router.beforeEach((to, from, next) => {
app.router.beforeEach((to, from, next) => {
    // 二重スラッシュを一重スラッシュに変換する。
// 二重スラッシュを一重スラッシュに変換する。
    // 素直にnextでリダイレクトすると、一度もともとのtoのURLでDOMの更新に進んでエラーが出る。
// 素直にnextでリダイレクトすると、一度もともとのtoのURLでDOMの更新に進んでエラーが出る。
    // そのためnext(false)でtoは使わずにpushで履歴を差し替える。
// そのためnext(false)でtoは使わずにpushで履歴を差し替える。
    if (/\/\/+/.test(to.path)) {
if (/\/\/+/.test(to.path)) {
      next(false)
next(false)
      app.router.push(to.path.replace(/\/\/+/g, '/'))
app.router.push(to.path.replace(/\/\/+/g, '/'))
    } else {
} else {
      next()
next()
    }
}
  })
})
}
}


== Vue.js ==
=== Vue.js ===


=== Naming ===
==== Naming ====
[https://qiita.com/ngron/items/ab2a17ae483c95a2f15e 【Vue】単一ファイルコンポーネントの命名規則まとめ【ファイル名から記法まで】 #Vue.js - Qiita]
[https://qiita.com/ngron/items/ab2a17ae483c95a2f15e 【Vue】単一ファイルコンポーネントの命名規則まとめ【ファイル名から記法まで】 #Vue.js - Qiita]


=== radioのbool ===
==== radioのbool ====
[https://stackoverflow.com/questions/45187048/vue-binding-radio-to-boolean vuejs2 - Vue: Binding radio to boolean - Stack Overflow]
[https://stackoverflow.com/questions/45187048/vue-binding-radio-to-boolean vuejs2 - Vue: Binding radio to boolean - Stack Overflow]


v-bind:か:で型を合わせて代入する。stringにするとうまく認識されない。
v-bind:か:で型を合わせて代入する。stringにするとうまく認識されない。


=== IMEの入力制限 ===
==== IMEの入力制限 ====
IME以外はformatterで処理して、IMEだけcompositionendで処理すればいい。
IME以外はformatterで処理して、IMEだけcompositionendで処理すればいい。
<!-- FormGroupText.vue -->
<!-- FormGroupText.vue -->
  <template>
  <template>
  <nowiki> </nowiki> <nowiki><div role="group" class="bv-no-focus-ring">
  <nowiki> </nowiki> <nowiki><div role="group" class="bv-no-focus-ring">
          <div
                    <div
            class="form-group-text"
                      class="form-group-text"
            :class="{ active: '' + value, 'is-invalid': error }"
                      :class="{ active: '' + value, 'is-invalid': error }"
          >
                    >
            <b-form-input
                      <b-form-input
              :id="'input-text-' + _uid"
                        :id="'input-text-' + _uid"
              type="text"
                        type="text"
              :formatter="formatter"
                        :formatter="formatter"
              :value="value"
                        :value="value"
              @input="$emit('input', $event)"
                        @input="$emit('input', $event)"
              @compositionend="$emit('input', normalize($event.target.value))"
                        @compositionend="$emit('input', normalize($event.target.value))"
              @paste="$emit('input', paste($event))"
                        @paste="$emit('input', paste($event))"
            />
                      />
            <label :for="'input-text-' + _uid">{{
                      <label :for="'input-text-' + _uid">{{
              /[._]/.test(label) ? $t(label) : label
                        /[._]/.test(label) ? $t(label) : label
            }}</nowiki><nowiki></label></nowiki>
                      }}</nowiki><nowiki></label></nowiki>
  <nowiki> </nowiki>  <nowiki></div></nowiki>
  <nowiki> </nowiki>  <nowiki></div></nowiki>
  <nowiki> </nowiki>  <nowiki><div :id="'input-live-feedback-' + _uid" class="invalid-feedback">
  <nowiki> </nowiki>  <nowiki><div :id="'input-live-feedback-' + _uid" class="invalid-feedback">
            {{ $t(error) }}</nowiki>
                      {{ $t(error) }}</nowiki>
  <nowiki> </nowiki>  <nowiki></div></nowiki>
  <nowiki> </nowiki>  <nowiki></div></nowiki>
  <nowiki> </nowiki> <nowiki></div></nowiki>
  <nowiki> </nowiki> <nowiki></div></nowiki>
  </template>
  </template>
  <nowiki><script>
  <nowiki><script>
      export default {
                export default {
        props: {
                  props: {
          value: { type: String, default: ''</nowiki>, required: true },
                    value: { type: String, default: ''</nowiki>, required: true },
  <nowiki> </nowiki>  label: { type: String, default: <nowiki>''</nowiki>, required: true },
  <nowiki> </nowiki>  label: { type: String, default: <nowiki>''</nowiki>, required: true },
  <nowiki> </nowiki>  // ファームウェアとスケジュールにエラーのないフォームがある。
  <nowiki> </nowiki>  // ファームウェアとスケジュールにエラーのないフォームがある。
195行目: 478行目:
  <template>
  <template>
  <nowiki> </nowiki> <nowiki><div role="group" class="bv-no-focus-ring mb-3">
  <nowiki> </nowiki> <nowiki><div role="group" class="bv-no-focus-ring mb-3">
          <div
                    <div
            class="form-group-text"
                      class="form-group-text"
            :class="{ active: '' + value, 'is-invalid': error }"
                      :class="{ active: '' + value, 'is-invalid': error }"
          >
                    >
            <b-form-input
                      <b-form-input
              :id="'input-number-' + _uid"
                        :id="'input-number-' + _uid"
              class="form-control"
                        class="form-control"
              type="tel"
                        type="tel"
              autocomplete="off"
                        autocomplete="off"
              maxlength="10"
                        maxlength="10"
              :formatter="formatter"
                        :formatter="formatter"
              :value="value"
                        :value="value"
              @input="$emit('input', $event)"
                        @input="$emit('input', $event)"
              @compositionend="$emit('input', normalize($event.target.value))"
                        @compositionend="$emit('input', normalize($event.target.value))"
            />
                      />
            <label :for="'input-number-' + _uid">{{ $t(label) }}</nowiki><nowiki></label></nowiki>
                      <label :for="'input-number-' + _uid">{{ $t(label) }}</nowiki><nowiki></label></nowiki>
  <nowiki> </nowiki>  <nowiki></div></nowiki>
  <nowiki> </nowiki>  <nowiki></div></nowiki>
  <nowiki> </nowiki>  <nowiki><div :id="'input-live-feedback-' + _uid" class="invalid-feedback">
  <nowiki> </nowiki>  <nowiki><div :id="'input-live-feedback-' + _uid" class="invalid-feedback">
            {{ $t(error) }}</nowiki>
                      {{ $t(error) }}</nowiki>
  <nowiki> </nowiki>  <nowiki></div></nowiki>
  <nowiki> </nowiki>  <nowiki></div></nowiki>
  <nowiki> </nowiki> <nowiki></div></nowiki>
  <nowiki> </nowiki> <nowiki></div></nowiki>
  </template>
  </template>
  <nowiki><script>
  <nowiki><script>
      export default {
                export default {
        props: {
                  props: {
          value: { type: [String, Number], default: ''</nowiki>, required: true },
                    value: { type: [String, Number], default: ''</nowiki>, required: true },
  <nowiki> </nowiki>  error: { type: String, default: <nowiki>''</nowiki>, required: true },
  <nowiki> </nowiki>  error: { type: String, default: <nowiki>''</nowiki>, required: true },
  <nowiki> </nowiki>  label: { type: String, default: <nowiki>''</nowiki>, required: true },
  <nowiki> </nowiki>  label: { type: String, default: <nowiki>''</nowiki>, required: true },
250行目: 533行目:
  <nowiki></script></nowiki>
  <nowiki></script></nowiki>


[[Category:JavaScript]]
===== error '<template>' cannot be keyed. Place the key on real elements instead vue/no-template-key =====
[https://stackoverflow.com/questions/56476413/custom-elements-in-iteration-require-v-bindkey-directives vue.js - Custom elements in iteration require 'v-bind:key' directives - Stack Overflow]
 
===== ハイライト =====
<nowiki>https://forum.vuejs.org/t/highlight-in-html-a-new-object-in-javascript-array/38877/9</nowiki>
 
nextTickでやればいいか。
 
[https://sagatto.com/20181031_highlight_display_at_vue_js 【Vue.js】検索文字などの特定文字を、マーカーでハイライト表示するコンポーネントを作った | SAGA.TXT]
 
検索キーワードで検索対象をsplitして、key/value形式で順番に配列で配置する。
 
===== カスタムコンポーネントのv-bind =====
[https://stackoverflow.com/questions/42918710/how-to-use-v-bind-in-a-custom-component javascript - How to use v-bind in a custom component? - Stack Overflow]
 
[https://github.com/buefy/buefy/issues/1038 using v-model b-input component · Issue #1038 · buefy/buefy]
 
 
 
 
== Template ==
 
 
=== Handlebars ===
 
* [https://anken-hyouban.com/blog/2021/06/09/handlebars-js/ Handlebars.jsを理解する!初心者でも分かる特徴、Mustacheとの違い、基礎知識などを簡単に解説! | 案件評判]
* [https://handlebarsjs.com/ Handlebars]
 
テンプレートエンジン。.hbsという拡張子のファイルがHandlebarsのテンプレートファイル。JavaScriptの変数値を参照してHTMLを生成できる。
 
<nowiki>{{}}のマスタッシュ記法でJSの変数をHTML内で参照する。</nowiki>
 
例えば、PHPの配列などのデータを、jsonに変換して、HTMLに埋め込み表示したりするのに使うことがある。
 
他に有名なテンプレートエンジンに、Mustacheというのがあるが、Handlebars.jsはそれより、高速で上位互換とのこと。
 
==== Introduction ====
[https://handlebarsjs.com/guide/#what-is-handlebars Introduction | Handlebars]
 
===== What is Handlebars? =====
以下のような式で、オブジェクトからHTMLを生成するテンプレートエンジン。
<nowiki><p>{{firstname}}</nowiki> <nowiki>{{lastname}}</nowiki><nowiki></p></nowiki>
なお、この二重波括弧はマスタッシュ記法と読んだりする。これは、ヒゲに似ているからで、他にMustache.jsというテンプレートエンジンの名前から。
 
===== Language features =====
 
====== Simple expressions ======
<nowiki><p>{{firstname}}</nowiki> <nowiki>{{lastname}}</nowiki><nowiki></p></nowiki>
 
{
firstname: "Yehuda",
lastname: "Katz",
}
 
<nowiki><p>Yehuda Katz</p></nowiki>
上記のように、マスタッシュ記法がJavaScriptのオブジェクトに変換される。
 
====== Nested input objects ======
オブジェクトや配列もJavaScriptで参照できる。
<nowiki><p>{{person.firstname}}</nowiki> <nowiki>{{person.lastname}}</nowiki><nowiki></p></nowiki>
 
{
person: {
firstname: "Yehuda",
lastname: "Katz",
},
}
 
====== Evaluation context ======
組み込みのブロックヘルパーのeachとwithは、コンテキストを評価できる。
 
withヘルパーはオブジェクトプロパティーのオブジェクトの前置を省略できる。
<nowiki>{{#with person}}</nowiki>
<nowiki>{{firstname}}</nowiki> <nowiki>{{lastname}}</nowiki>
<nowiki>{{/with}}</nowiki>
 
{
person: {
firstname: "Yehuda",
lastname: "Katz",
},
}
eachヘルパーは配列。
<nowiki><ul class="people_list">
{{#each people}}</nowiki>
<nowiki> </nowiki>  <nowiki><li>{{this}}</nowiki><nowiki></li></nowiki>
<nowiki> </nowiki> <nowiki>{{/each}}</nowiki>
<nowiki></ul></nowiki>
 
{
people: [
"Yehuda Katz",
"Alan Johnson",
"Charles Jolley",
],
}
 
<nowiki><ul class="people_list"></nowiki>
<nowiki><li>Yehuda Katz</li></nowiki>
<nowiki><li>Alan Johnson</li></nowiki>
<nowiki><li>Charles Jolley</li></nowiki>
<nowiki></ul></nowiki>
 
====== Template comments ======
handlebarsコードのコメントは、以下の2種類が可能。通常のHTMLコメントも問題ないが、HTMLをソースコードとして表示すると、コード上には表示される。
<nowiki>{{! }}</nowiki>
<nowiki>{{!-- }}</nowiki> --}}
コメント内に、}}を含めたい場合は、<nowiki>{{!-- --}}</nowiki>の記法を使う必要がある。
 
==== Block Helpers ====
[https://handlebarsjs.com/guide/block-helpers.html Block Helpers | Handlebars]
 
===== Simple Iterators =====
ブロックヘルパーの主な用途は、カスタムイテレーターの定義時の使用だ。eachヘルパーの使用例は以下だ。<syntaxhighlight lang="html">
<div class="entry">
  <h1>{{title}}</h1>
  {{#with story}}
    <div class="intro">{{{intro}}}</div>
    <div class="body">{{{body}}}</div>
  {{/with}}
</div>
<div class="comments">
  {{#each comments}}
    <div class="comment">
      <h2>{{subject}}</h2>
      {{{body}}}
    </div>
  {{/each}}
</div>
</syntaxhighlight>このケースでは、eatchにコメントの配列を渡している。
Handlebars.registerHelper("each", function(context, options) {
  var ret = "";
 
for (var i = 0, j = context.length; i < j; i++) {
ret = ret + options.fn(context[i]);
}
 
return ret;
});
 
==== Built-in Helpers ====
[https://handlebarsjs.com/guide/builtin-helpers.html Built-in Helpers | Handlebars]
 
===== #if =====
引数の変数がfalse, undefined, null, "", 0, or []以外ならtrueと評価。
 
この#ifは値を渡すことしかできず、式を一切使えない。反転したい場合は、#unlessを指定する。
 
あるいは、カスタムヘルパー関数を作る。
 
===== #unless =====
 
===== #each =====
eachを使うことで、リストを反復できる。ブロック内で、thisを要素の参照に使える。<syntaxhighlight lang="handlebars">
<ul class="people_list">
  {{#each people}}
    <li>{{this}}</li>
  {{/each}}
</ul>
</syntaxhighlight>
{
  people: [
    "Yehuda Katz",
    "Alan Johnson",
    "Charles Jolley",
  ],
}
 
<nowiki><ul class="people_list"></nowiki>
<nowiki><li>Yehuda Katz</li></nowiki>
<nowiki><li>Alan Johnson</li></nowiki>
<nowiki><li>Charles Jolley</li></nowiki>
<nowiki></ul></nowiki>
<nowiki>{ {else}} を配列が空の場合の表示に使える。</nowiki>
 
<nowiki>オブジェクトの反復では、{ {@key}} で現在のキー名を参照できる。</nowiki><syntaxhighlight lang="handlebars">
{{#each object}} {{@key}}: {{this}} {{/each}}
</syntaxhighlight>なお、公式マニュアルに明記されていないが、each内で要素がオブジェクトの場合、this.を省略して、いきなりプロパティーにアクセスできる (https://chatgpt.com/c/67e50098-cbd4-800b-89f5-501b03f4d6d2<nowiki/>)。<syntaxhighlight lang="handlebars">
<ul>
  {{#each people}}
    <li>{{name}}</li>
  {{/each}}
</ul>
 
<ul>
  {{#each people}}
    <li>{{this.name}}</li>
  {{/each}}
</ul>
 
<script>
    const context = {
  people: [
    { name: "Yehuda" },
    { name: "Carl" },
    { name: "Alan" }
  ]
};
</script>
</syntaxhighlight>変数名が、外と衝突する恐れがあるので、thisを明示した方が安全に感じる。

2025年3月28日 (金) 18:27時点における最新版

Tool

Grunt

About

JavaScriptのタスクランナー。ビルドやファイルの整形、変換などを行ってくれる。

gruntの他にgulpというのもある。

Grunt: The JavaScript Task Runner

grunt vs. gulp

絶対つまずかないGulp 5入門 - インストールとSassを使うまでの手順 - ICS MEDIA

設定ファイルが違う。

  • grunt: JSONで同期。
  • gulp: JavaScriptで非同期。

Gruntfile

Gruntfile.jsかGruntfile.coffeがタスクの定義ファイル。

以下のコマンドでgruntで実行可能なタスク一覧が表示される。 grunt --help 単にgruntとだけ実行すると、defaultのタスクが実行される。基本はサブコマンドを指定する。

Node.js

Install

Node.js — Download Node.js®

ここでプラットフォーム別のインストール方法がある。

Linuxの場合、以下。

  1. installs nvm (Node Version Manager)

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash

  1. download and install Node.js (you may need to restart the terminal)

nvm install 22

  1. verifies the right Node.js version is in the environment

node -v # should print `v22.12.0`

  1. verifies the right npm version is in the environment

npm -v # should print `10.9.0` 最初の行を実行してインストールしたら~/.bashrcに以下の内容が追記される。即座にインストールを反映したければ現在の端末でも実行する。 export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion

Framework

Ember.js

Angular/Vue/ReactのようなJavaScriptフレームワーク。クライアントサイドのMVCフレームワーク。サーバーからデータをもらって、それをJSで描画するタイプ。

2011年に誕生。

Getting Started

重要な基本概念がある。それらを簡単に整理。

  • Template: MVCのV担当。表示用HTMLのテンプレートhandlebars.jsを使っている。Modelの変数を使って表示する。
  • Component: Templateを機能単位で固めたもの。
  • Router: ルーティング。リクエストURLに対する処理を行う。このリクエストに対して、Modelからデータを取得してVで表示する。MVCのC相当。
  • Model: 永続化データの格納オブジェクト。サーバーからJSON APIで取得した結果を変数 (Modelオブジェクト) に格納する。
  • Route: どのモデルを表示するかをTemplateに伝えるオブジェクト。
  • Controller:

基本的な構造。

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World - Ember.js</title>
</head>
<body>
	<script type="text/x-handlebars">
<h1>Hello World</h1>
<p>Hello, {{name}}</p>
	</script>
	<script type="text/javascript" src="js/libs/jquery-1.9.1.js"></script>
	<script type="text/javascript" src="js/libs/handlebars-1.0.0.js"></script>
	<script type="text/javascript" src="js/libs/ember-1.0.0-rc.7.js"></script>
	<script type="text/javascript">
window.App = Ember.Application.create();
App.ApplicationRoute = Ember.Route.extend({
	model: function() {
		return {name: 'Ember.js'};
	}
});
	</script>
</body>
</html>
Obtaining Ember.js and Dependencies

How To Use The Guides - Getting Started - Ember Guides

  <script src="js/libs/jquery-1.11.2.min.js"></script>
  <script src="js/libs/handlebars-v1.3.0.js"></script>
  <script src="js/libs/ember.js"></script>
  <script src="js/libs/ember-data.js"></script>
</body>

Concepts

Naming Conventions

Naming Conventions - Concepts - Ember Guides

いくつかの命名規則で、ルーティングが決まる。

The Application

アプリの起動時に、以下のオブジェクトを探す。

  • App.ApplicationRoute
  • App.ApplicationController
  • the application template

これがデフォルトのルートみたいなもの。

The Index Route

Emberは自動的に、/のパスをindexのとみなす。

  • App.IndexRoute
  • App.IndexController
  • the index template

上記になる。

Templates

How To Use The Guides - Getting Started - Ember Guides

Handlebars Basics

How To Use The Guides - Getting Started - Ember Guides

Ember.jsはHandlebarsテンプレートライブラリーを使っている。

HTMLのテンプレート。表示部分。

Actions

Actions - Templates - Ember Guides

{{action}} helper

アプリで、ユーザーがアプリの状態を変更するコントローラーを操作できるようにする方法が必要になる。例えば、ボタンを押したら、処理を行うような感じ。

<!-- post.handlebars -->

<div class='intro'>
  {{intro}}
</div>

{{#if isExpanded}}
  <div class='body'>{{body}}</div>
  <button {{action 'contract'}}>Contract</button>
{{else}}
  <button {{action 'expand'}}>Show More...</button>
{{/if}}

<script>
    App.PostController = Ember.ObjectController.extend({
  // initial value
  isExpanded: false,

  actions: {
    expand: function() {
      this.set('isExpanded', true);
    },

    contract: function() {
      this.set('isExpanded', false);
    }
  }
});
</script>

コントローラーのactionオブジェクト内の同じ名前のメソッドを使う。

Routing

Defining Your Routes

Defining Your Routes - Routing - Ember Guides

アプリケーションの開始時に、現在URLにマッチするルートのControllerを表示する。そのルーティングを定義する。

いくつかの方法がある。

まずシンプルなのがmapメソッドを使う方法。

App.Router.map(function() {
  this.route('about', { path: '/about' });
  this.route('favorites', { path: '/favs' });
});

例えば、/aboutにアクセス時に、aboutテンプレートを描画する。

pathを省略すると、テンプレート名をpathとみなす。

他に、Ember.Routeのサブクラスとして実装する方法もある。

App.IndexRoute = Ember.Route.extend({
  setupController: function(controller) {
    // Set the IndexController's `title`
    controller.set('title', 'My App');
  }
});

上記のようにすると、IndexControllerをindexテンプレートとともに使う。Routeの前の部分の小文字をベースネームとして使う。

URL Route Name Controller Route Template
/ index IndexController IndexRoute index
/about about AboutController AboutRoute about
/favs favorites FavoritesController FavoritesRoute favorites

こういう対応関係。

Dynamic Segments

URLをモデルに変換することもできる。

App.PostsRoute = Ember.Route.extend({
  model: function() {
    return this.store.find('post');
  }
});

これで/postにアクセス時にstoreのpostオブジェクトを渡す。

Specifying a Route's Model

Defining Your Routes - Routing - Ember Guides

アプリのテンプレートはmodelを背景に持つ。テンプレートがどのモデルを持つかをmodelプロパティーのフックに定義する。

App.PhotosRoute = Ember.Route.extend({
  model: function() {
    return return {name: 'Ember.js'};
  }
});

こんな感じで定義したら、template内でnameの変数が使える感じ。たぶん、thisの省略形?

Controllers

Introduction - Controllers - Ember Guides

init

Controllerのインスタンス生成、初期化時に呼び出す処理をinitに登録できる。コンストラクター的なもの。

jQuery

jQuery API Documentation

レガシー

jQueryがもういらなくなってきたという意見がある。

  • Webブラウザー間の違いがなくなってきた。
  • 標準のJavaScriptでできることが増えた。

上記2点が大きい。jQueryじゃないとできないことが大幅に減った。標準APIでこなしたほうがいいだろう。

ajaxのasync/awaitでの書換

$.ajax(jQuery.ajax)の非同期処理をasync awaitの入れ子なし同期処理で記述する #JavaScript - Qiita

ajaxを使っている関数の定義にasync。ajax関数の使用箇所にawait。これでうまくいく。

  • フルーツ名を取得する
  • @param {string} fruitId
 */
 async function getFruitName(fruitId) {
 const fruitRequest = {id: fruitId}
 const fruitResult = await ajaxGetFruit(fruitRequest);
 return fruitResult.name;
 }

/**

  • フルーツのajax[GET]を実施する
  • @param {object} request
  • @returns
 */
 function ajaxGetFruit(request) {
 return $.ajax({
 url: '/fruit/name',
 type: "GET",
 async: true,
 contentType: "application/json",
 data: JSON.stringify(request),
 dataType: "json",
 }).then(
 function (result) {
 // 正常終了
 resolve(result);
 },
 function () {
 // エラー
 reject();
 }
 )
 }
 「jQuery.ajax() | jQuery API Documentation」にあるように、jquery 1.5から$.ajaxの返却値はPromiseの派生クラス。そのまま返却させて良い。

DataTables

jqueryのプラグイン。テーブル処理をうまくやってくれる。

Option
Old

DataTables (table plug-in for jQuery)

v1.9以下の古いバージョンのサイトはこちらにある。

ServerSide

Server-side processing

通常はクライアント画面のみ。全データを検索したい場合、serverSideオプションが必要。ただし、このオプションはdatatablesのv1.10以上じゃない。

Pagination

ページ表示件数に関するオプションがいくつかある。

  • pageLength/iDisplayLength: 1ページあたりの表示件数。デフォルトは10。
  • lengthMenu/aLengthMenu: リストボックスの項目。デフォルトは[10, 25, 50, 100]。-1 ("All") で全表示。

Nuxt.js

終端スラッシュありへのリダイレクト

Nuxt.jsにおける末尾スラッシュを統一する方法 #amplify - Qiita // middleware/trailingSlash.js /**

  • 終端スラッシュありのURLにリダイレクトする。
  • @param {Object} Nuxt.jsのContextオブジェクト。
 */
 export default ({ route, redirect }) => {
 if (route.path.endsWith('/')) {
 return
 }
 let to = route.path + '/'
 if (route.query) {
 to +=
 '?' +
 Object.entries(route.query)
 .map((e) => e.join('='))
 .join('&')
 }
 to += route.hash
 redirect(301, to)
 }

//へのアクセスエラー

  • https://github.com/vuejs/vue-router/issues/2593
  • https://github.com/nuxt/nuxt.js/issues/2020

けっきょく、serverMiddlewareは結合環境で機能せず、middlewareもルートの変化後に発動するため対応できなかった。

そのため、プラグインにして、router.beforeEachで変化前に書き換え処理を入れた。 // plugins/redirect.js export default ({ app }) => { app.router.beforeEach((to, from, next) => { // 二重スラッシュを一重スラッシュに変換する。 // 素直にnextでリダイレクトすると、一度もともとのtoのURLでDOMの更新に進んでエラーが出る。 // そのためnext(false)でtoは使わずにpushで履歴を差し替える。 if (/\/\/+/.test(to.path)) { next(false) app.router.push(to.path.replace(/\/\/+/g, '/')) } else { next() } }) }

Vue.js

Naming

【Vue】単一ファイルコンポーネントの命名規則まとめ【ファイル名から記法まで】 #Vue.js - Qiita

radioのbool

vuejs2 - Vue: Binding radio to boolean - Stack Overflow

v-bind:か:で型を合わせて代入する。stringにするとうまく認識されない。

IMEの入力制限

IME以外はformatterで処理して、IMEだけcompositionendで処理すればいい。

<template>
  <div role="group" class="bv-no-focus-ring">
                    <div
                      class="form-group-text"
                      :class="{ active: '' + value, 'is-invalid': error }"
                    >
                      <b-form-input
                        :id="'input-text-' + _uid"
                        type="text"
                        :formatter="formatter"
                        :value="value"
                        @input="$emit('input', $event)"
                        @compositionend="$emit('input', normalize($event.target.value))"
                        @paste="$emit('input', paste($event))"
                      />
                      <label :for="'input-text-' + _uid">{{
                        /[._]/.test(label) ? $t(label) : label
                      }}</label>
    </div>
    <div :id="'input-live-feedback-' + _uid" class="invalid-feedback">
                      {{ $t(error) }}
    </div>
  </div>
</template>
<script>
                export default {
                  props: {
                    value: { type: String, default: '', required: true },
    label: { type: String, default: '', required: true },
    // ファームウェアとスケジュールにエラーのないフォームがある。
    error: { type: String, default: '' },
  },
  methods: {
    /**
     * 入力内容の正規化を行う。
     * @param {string} input - 入力文字列。
     * @return {string} 正規化後文字列。
     */
    normalize(input) {
      // ASCII文字列以外は削除する。
      return input.replace(/[^ -~]/g, '')
    },
    /**
     * 入力フォームを整形する。
     * @param {string} value - 入力文字列。
     * @param {Object} event - イベントオブジェクト。
     * @return {string} IME変換時は正規化後の文字列。それ以外は、compositionendで行うため、未変換文字列。
     * IME変換時はcompositionendで入力を正規化し、それ以外はformatterで正規化する。
     */
    formatter(value, event) {
      return event.isComposing ? value : this.normalize(value)
    },
    /**
     * クリップボードからの貼付時の文字列を処理する。
     * @param {Object} event - イベントオブジェクト。
     * @return {string} 処理後の文字列。
     * @todo 入力の途中にカーソルを移動させて貼り付けた場合、貼り付け後、カーソルが末尾に移動してしまう。
     * event.preventDefault()が原因と思われる。しかし、これの解決が難しい。
     * event.target.selectionStartにカーソル位置を指定できるのだが、preventDefaultすると一瞬カーソルが末尾に飛ぶ。
     */
    paste(event) {
      event.preventDefault()
      const cb = (event.clipboardData || window.clipboardData).getData('text')
      const start = event.target.selectionStart
      return this.normalize(
        this.value.slice(0, start) + cb + this.value.slice(start)
      )
    },
  },
}
</script>
<template>
  <div role="group" class="bv-no-focus-ring mb-3">
                    <div
                      class="form-group-text"
                      :class="{ active: '' + value, 'is-invalid': error }"
                    >
                      <b-form-input
                        :id="'input-number-' + _uid"
                        class="form-control"
                        type="tel"
                        autocomplete="off"
                        maxlength="10"
                        :formatter="formatter"
                        :value="value"
                        @input="$emit('input', $event)"
                        @compositionend="$emit('input', normalize($event.target.value))"
                      />
                      <label :for="'input-number-' + _uid">{{ $t(label) }}</label>
    </div>
    <div :id="'input-live-feedback-' + _uid" class="invalid-feedback">
                      {{ $t(error) }}
    </div>
  </div>
</template>
<script>
                export default {
                  props: {
                    value: { type: [String, Number], default: '', required: true },
    error: { type: String, default: '', required: true },
    label: { type: String, default: '', required: true },
  },
  methods: {
    /**
     * 入力内容の正規化を行う。
     * @param {string} value - 入力文字列。
     * @return {string} result - 正規化後文字列。
     * @warn v-model.numberの修飾子を指定するとcompositionendでの変更が反映されない。
     * そのため、.numberを使わず、この関数で型変換する。
     */
    normalize(value) {
      const result = parseFloat(value.replace(/[^0-9]/g, ''))
      return isNaN(result) ? '' : result
    },
    /**
     * 入力フォームを整形する。
     * @param {string} value - 入力文字列。
     * @param {Object} event - イベントオブジェクト。
     * @return {string} IME変換時は正規化後の文字列。それ以外は、compositionendで行うため、未変換文字列。
     * IME変換時はcompositionendで入力を正規化し、それ以外はformatterで正規化する。
     */
    formatter(value, event) {
      return event.isComposing ? value : this.normalize(value)
    },
  },
}
</script>
error '<template>' cannot be keyed. Place the key on real elements instead vue/no-template-key

vue.js - Custom elements in iteration require 'v-bind:key' directives - Stack Overflow

ハイライト

https://forum.vuejs.org/t/highlight-in-html-a-new-object-in-javascript-array/38877/9

nextTickでやればいいか。

【Vue.js】検索文字などの特定文字を、マーカーでハイライト表示するコンポーネントを作った | SAGA.TXT

検索キーワードで検索対象をsplitして、key/value形式で順番に配列で配置する。

カスタムコンポーネントのv-bind

javascript - How to use v-bind in a custom component? - Stack Overflow

using v-model b-input component · Issue #1038 · buefy/buefy



Template

Handlebars

テンプレートエンジン。.hbsという拡張子のファイルがHandlebarsのテンプレートファイル。JavaScriptの変数値を参照してHTMLを生成できる。

{{}}のマスタッシュ記法でJSの変数をHTML内で参照する。

例えば、PHPの配列などのデータを、jsonに変換して、HTMLに埋め込み表示したりするのに使うことがある。

他に有名なテンプレートエンジンに、Mustacheというのがあるが、Handlebars.jsはそれより、高速で上位互換とのこと。

Introduction

Introduction | Handlebars

What is Handlebars?

以下のような式で、オブジェクトからHTMLを生成するテンプレートエンジン。 <p>{{firstname}} {{lastname}}</p> なお、この二重波括弧はマスタッシュ記法と読んだりする。これは、ヒゲに似ているからで、他にMustache.jsというテンプレートエンジンの名前から。

Language features
Simple expressions

<p>{{firstname}} {{lastname}}</p>

{ firstname: "Yehuda", lastname: "Katz", }

<p>Yehuda Katz</p> 上記のように、マスタッシュ記法がJavaScriptのオブジェクトに変換される。

Nested input objects

オブジェクトや配列もJavaScriptで参照できる。 <p>{{person.firstname}} {{person.lastname}}</p>

{ person: { firstname: "Yehuda", lastname: "Katz", }, }

Evaluation context

組み込みのブロックヘルパーのeachとwithは、コンテキストを評価できる。

withヘルパーはオブジェクトプロパティーのオブジェクトの前置を省略できる。 {{#with person}} {{firstname}} {{lastname}} {{/with}}

{ person: { firstname: "Yehuda", lastname: "Katz", }, } eachヘルパーは配列。 <ul class="people_list"> {{#each people}} <li>{{this}}</li> {{/each}} </ul>

{ people: [ "Yehuda Katz", "Alan Johnson", "Charles Jolley", ], }

<ul class="people_list"> <li>Yehuda Katz</li> <li>Alan Johnson</li> <li>Charles Jolley</li> </ul>

Template comments

handlebarsコードのコメントは、以下の2種類が可能。通常のHTMLコメントも問題ないが、HTMLをソースコードとして表示すると、コード上には表示される。 {{! }} {{!-- }} --}} コメント内に、}}を含めたい場合は、{{!-- --}}の記法を使う必要がある。

Block Helpers

Block Helpers | Handlebars

Simple Iterators

ブロックヘルパーの主な用途は、カスタムイテレーターの定義時の使用だ。eachヘルパーの使用例は以下だ。

<div class="entry">
  <h1>{{title}}</h1>
  {{#with story}}
    <div class="intro">{{{intro}}}</div>
    <div class="body">{{{body}}}</div>
  {{/with}}
</div>
<div class="comments">
  {{#each comments}}
    <div class="comment">
      <h2>{{subject}}</h2>
      {{{body}}}
    </div>
  {{/each}}
</div>

このケースでは、eatchにコメントの配列を渡している。

Handlebars.registerHelper("each", function(context, options) {
  var ret = "";

for (var i = 0, j = context.length; i < j; i++) { ret = ret + options.fn(context[i]); }

return ret; });

Built-in Helpers

Built-in Helpers | Handlebars

#if

引数の変数がfalse, undefined, null, "", 0, or []以外ならtrueと評価。

この#ifは値を渡すことしかできず、式を一切使えない。反転したい場合は、#unlessを指定する。

あるいは、カスタムヘルパー関数を作る。

#unless
#each

eachを使うことで、リストを反復できる。ブロック内で、thisを要素の参照に使える。

<ul class="people_list">
  {{#each people}}
    <li>{{this}}</li>
  {{/each}}
</ul>
{
  people: [
    "Yehuda Katz",
    "Alan Johnson",
    "Charles Jolley",
  ],
}

<ul class="people_list"> <li>Yehuda Katz</li> <li>Alan Johnson</li> <li>Charles Jolley</li> </ul> { {else}} を配列が空の場合の表示に使える。

オブジェクトの反復では、{ {@key}} で現在のキー名を参照できる。

{{#each object}} {{@key}}: {{this}} {{/each}}

なお、公式マニュアルに明記されていないが、each内で要素がオブジェクトの場合、this.を省略して、いきなりプロパティーにアクセスできる (https://chatgpt.com/c/67e50098-cbd4-800b-89f5-501b03f4d6d2)。

<ul>
  {{#each people}}
    <li>{{name}}</li>
  {{/each}}
</ul>

<ul>
  {{#each people}}
    <li>{{this.name}}</li>
  {{/each}}
</ul>

<script>
    const context = {
  people: [
    { name: "Yehuda" },
    { name: "Carl" },
    { name: "Alan" }
  ]
};
</script>

変数名が、外と衝突する恐れがあるので、thisを明示した方が安全に感じる。