「JavaScript library」の版間の差分

提供:senooken JP Wiki
(Handlebars Introduction)
(Built-in Helpers #each)
190行目: 190行目:
  <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>  // ファームウェアとスケジュールにエラーのないフォームがある。
263行目: 263行目:
  <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 },
420行目: 420行目:
  <nowiki>{{!-- }}</nowiki> --}}
  <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]
==== #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を明示した方が安全に感じる。
[[Category:JavaScript]]
[[Category:JavaScript]]

2025年3月28日 (金) 11:33時点における版

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の場合、以下。

# installs nvm (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | 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

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

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

#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を明示した方が安全に感じる。