「JavaScript library」の版間の差分

提供:senooken JP Wiki
(Vue.jsメモ。)
(Pakage manager NPM Install idealTree:lib: sill idealTree buildDeps)
 
(同じ利用者による、間の23版が非表示)
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
 
=== Pakage manager ===
 
==== NPM ====
 
===== Install =====
 
====== idealTree:lib: sill idealTree buildDeps ======
npm installを実行すると上記のメッセージで固まる。
 
いろんな原因が考えられる。
npm config set proxy http://proxy.example.com
プロキシーサーバーがある場合、上記で設定するとうまくいく。自分の場合はこれだった。
 
== 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行目: 289行目:
上記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") で全表示。
 
=== Vue.js ===
 
==== Essentials ====
 
===== Template Syntax =====
[https://v2.vuejs.org/v2/guide/syntax Template Syntax — Vue.js]
 
====== Attributes ======
Mustaches記法はHTMLの属性内では使用できない。代わりに、v-bind指令を使う。
<nowiki><div v-bind:id="dynamicId"></div></nowiki>
v-modelと似ている。v-modelはユーザーの入力 (value) を受けて連動するという違いがある。v-bindは一方向で基本的には静的。
 
===== Conditional Rendering =====
[https://v2.vuejs.org/v2/guide/conditional Conditional Rendering — Vue.js]
 
ifで条件に応じた描画ができる。両方とも重要。


===== List Rendering =====
[https://v2.vuejs.org/v2/guide/list List Rendering — Vue.js]


== Nuxt.js ==
v-forで反復可能。<syntaxhighlight lang="html">
<ul id="example-1">
  <li v-for="item in items" :key="item.message">
    {{ item.message }}
  </li>
</ul>
</syntaxhighlight>:keyを指定すると、並べ替えなどでVueが識別可能になる。


=== 終端スラッシュありへのリダイレクト ===
===== Form Input Bindings =====
[https://qiita.com/too/items/1b0944f5acb2aeb4e9b9 Nuxt.jsにおける末尾スラッシュを統一する方法 #amplify - Qiita]
[https://v2.vuejs.org/v2/guide/forms Form Input Bindings — Vue.js]
// 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)
}


=== //へのアクセスエラー ===
====== Basic Usage ======
v-model指令を、formのinput/textarea/select要素の双方向のデータ束縛に使える。


* <nowiki>https://github.com/vuejs/vue-router/issues/2593</nowiki>
==== API ====
* <nowiki>https://github.com/nuxt/nuxt.js/issues/2020</nowiki>


けっきょく、serverMiddlewareは結合環境で機能せず、middlewareもルートの変化後に発動するため対応できなかった。
===== Options / Data =====
データ関係のプロパティーがある。


そのため、プラグインにして、router.beforeEachで変化前に書き換え処理を入れた。
[https://v2.vuejs.org/v2/api/ API — Vue.js]
// 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 ==
* data
* methods
* watch


=== 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行目: 486行目:
  <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行目: 541行目:
  <nowiki></script></nowiki>
  <nowiki></script></nowiki>


==== error '<template>' cannot be keyed. Place the key on real elements instead vue/no-template-key ====
===== 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]
[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>
<nowiki>https://forum.vuejs.org/t/highlight-in-html-a-new-object-in-javascript-array/38877/9</nowiki>


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


==== カスタムコンポーネントのv-bind ====
===== カスタムコンポーネントの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://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]
[https://github.com/buefy/buefy/issues/1038 using v-model b-input component · Issue #1038 · buefy/buefy]
[[Category:JavaScript]]
=== Nuxt.js ===
 
==== Get Started ====
[https://v2.nuxt.com/ja/docs/get-started/installation Nuxt 2 - インストール]
 
===== Install =====
[https://nuxt.com/docs/4.x/getting-started/installation Installation · Get Started with Nuxt v4]
 
インストール方法がいろいろあって面倒くさい。
 
v2系は以下のコマンドでインストールするらしい。
npx create-nuxt-app@3 frontend
 
===== Commands =====
[https://v2.nuxt.com/ja/docs/get-started/commands Nuxt 2 - コマンドと開発]
npm run generate
このコマンドを実行すると、Nuxtの全部の静的ファイルが格納されたdistディレクトリーが作られる。これをサーバーのpublicとかに配置すると良い。
 
generate.dirを直接publicにしてもいい。その場合、public以下のVCSでの管理対象の工夫が必要。
 
https://chatgpt.com/share/68846c45-1d20-800b-9351-276c3a2be352
# Nuxt generate output (Laravel public に出力)
!/backend/public/.gitkeep
/backend/public/_nuxt
/backend/public/**/*.html
/backend/public/**/*.js
/backend/public/**/*.css
上記のように、nuxtの生成物は除外する。
 
===== Start the project =====
以下のコマンドでローカルにサーバーを設置できて、ファイルの変更を即座に反映してくれる。
npm run dev
<nowiki>http://localhost:3000</nowiki> がデフォルト。npm run generateすると、全ファイルを生成するので時間がかかる。開発中はローカルサーバーでやった方が早い。
 
なお、デフォルトがlocalhostになっていて、Dockerのホストからアクセスできない。
 
[https://k-markup.com/blog/cannot-access-nuxt-in-container/ DockerコンテナのNuxtにlocalhostでアクセスできないとき]
export default {
  server: {
    // port: 8000, // デフォルト: 3000
    host: '0.0.0.0', // デフォルト: localhost,
    // timing: false
  }
}
上記のhostの設定でlocalhostから0.0.0.0にしたらアクセス可能になる。
 
なお、npm run devのサーバーは開発用で、負荷には耐えられない。複数タブを開いたままにしたりすると、状態の監視などで負荷が高い。動作が遅いと思ったら、タブを閉じた方がいい。
 
==== Concept ====
 
===== 静的サイト生成 =====
 
====== About ======
[https://v2.nuxt.com/ja/docs/concepts/static-site-generation Nuxt 2 - 静的サイト生成]
 
Static Site Generation (SSG)。
 
npm run generateでdist以下に静的データが出力される。これをpublicなどに配置するとそのままフロント用のアプリサーバーなしで動作するというもの。
 
====== 動的ルーティング ======
[https://hapicode.com/javascript/nuxt-generate.html#%E5%8B%95%E7%9A%84%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%AF%E3%82%99%E3%81%AE%E3%83%8F%E3%82%9A%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E5%8F%96%E5%BE%97 Nuxtjs 動的なルーティング静的ページ自動生成 | Hapicode]
 
https://chatgpt.com/share/6886c2c2-b5cc-800b-8e03-e301c22d5eff
 
ユーザーページなど、動的ルーティングを含んでいる場合の静的ページ作成は2通りの方法がある。
 
nuxt.config.jsなどで設定する。
import axios from 'axios'
export default {
  generate: {
    routes: async () => {
      const { data: users } = await axios.get('<nowiki>https://example.com/api/users'</nowiki>)
      return users.map(user => `/user/${user.id}`)
    }
  }
}
上記のように、静的サイト作成時に、動的ルーティングの前一覧を取得して、/user/1なども全部作成するというのが1個の方法。ただ、この方法だと後から追加された場合に対応できない。
export default {
  generate: {
    fallback: true  // ← これがポイント!
  }
}
generate.fallbackを指定するといいらしい。trueだと404.htmlになる。他はHTMLファイルのパス。
 
==== Directory Structure ====
 
===== pages =====
[https://v2.nuxt.com/ja/docs/directory-structure/pages Nuxt 2 - ページディレクトリ]
 
pagesに配置したvueファイルがそのまま公開される。
 
URLパスに、動的パラメーターがある場合、_から始まるファイル名にする。
 
_slug.vueファイルにしたら、params.slugで値にアクセスできる。<syntaxhighlight lang="html">
<template>
  <h1>{{ this.slug }}</h1>
</template>
 
<script>
  export default {
    async asyncData({ params }) {
      const slug = params.slug // "/abc" パスにアクセスすると、slug は "abc" になります。
      return { slug }
    }
  }
</script>
</syntaxhighlight>
 
===== nuxt.config.js =====
[https://v2.nuxt.com/ja/docs/directory-structure/nuxt-config/ Nuxt 2 - Nuxt 設定ファイル]
 
Nuxtの設定。
 
generate: HTMLファイル変換時のパラメーターを設定。dir=distの代わりの配置場所。
 
 
 
==== axios ====
[https://axios.nuxtjs.org/ Introduction - Axios Module]
 
Frontendの有名HTTPクライアントライブラリー。
 
===== Setup =====
[https://axios.nuxtjs.org/setup Setup - Axios Module]
 
@nuxtjs/axiosを依存関係に追加する。
npm install @nuxtjs/axios
nuxt.config.jsにmodulesで追加する。
export default {
  modules: ['@nuxtjs/axios']
}
これを指定すると、$axiosでNuxt内で参照可能になる。
 
==== Other ====
 
===== 終端スラッシュありへのリダイレクト =====
[https://qiita.com/too/items/1b0944f5acb2aeb4e9b9 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)
  }
 
===== //へのアクセスエラー =====
* <nowiki>https://github.com/vuejs/vue-router/issues/2593</nowiki>
* <nowiki>https://github.com/nuxt/nuxt.js/issues/2020</nowiki>
 
けっきょく、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()
}
})
}
 
 
 
== 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年9月11日 (木) 17:18時点における最新版

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

Pakage manager

NPM

Install
idealTree:lib: sill idealTree buildDeps

npm installを実行すると上記のメッセージで固まる。

いろんな原因が考えられる。

npm config set proxy http://proxy.example.com

プロキシーサーバーがある場合、上記で設定するとうまくいく。自分の場合はこれだった。

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") で全表示。

Vue.js

Essentials

Template Syntax

Template Syntax — Vue.js

Attributes

Mustaches記法はHTMLの属性内では使用できない。代わりに、v-bind指令を使う。

<div v-bind:id="dynamicId"></div>

v-modelと似ている。v-modelはユーザーの入力 (value) を受けて連動するという違いがある。v-bindは一方向で基本的には静的。

Conditional Rendering

Conditional Rendering — Vue.js

ifで条件に応じた描画ができる。両方とも重要。

List Rendering

List Rendering — Vue.js

v-forで反復可能。

<ul id="example-1">
  <li v-for="item in items" :key="item.message">
    {{ item.message }}
  </li>
</ul>

:keyを指定すると、並べ替えなどでVueが識別可能になる。

Form Input Bindings

Form Input Bindings — Vue.js

Basic Usage

v-model指令を、formのinput/textarea/select要素の双方向のデータ束縛に使える。

API

Options / Data

データ関係のプロパティーがある。

API — Vue.js

  • data
  • methods
  • watch

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

Nuxt.js

Get Started

Nuxt 2 - インストール

Install

Installation · Get Started with Nuxt v4

インストール方法がいろいろあって面倒くさい。

v2系は以下のコマンドでインストールするらしい。

npx create-nuxt-app@3 frontend
Commands

Nuxt 2 - コマンドと開発

npm run generate

このコマンドを実行すると、Nuxtの全部の静的ファイルが格納されたdistディレクトリーが作られる。これをサーバーのpublicとかに配置すると良い。

generate.dirを直接publicにしてもいい。その場合、public以下のVCSでの管理対象の工夫が必要。

https://chatgpt.com/share/68846c45-1d20-800b-9351-276c3a2be352

# Nuxt generate output (Laravel public に出力)
!/backend/public/.gitkeep
/backend/public/_nuxt
/backend/public/**/*.html
/backend/public/**/*.js
/backend/public/**/*.css

上記のように、nuxtの生成物は除外する。

Start the project

以下のコマンドでローカルにサーバーを設置できて、ファイルの変更を即座に反映してくれる。

npm run dev

http://localhost:3000 がデフォルト。npm run generateすると、全ファイルを生成するので時間がかかる。開発中はローカルサーバーでやった方が早い。

なお、デフォルトがlocalhostになっていて、Dockerのホストからアクセスできない。

DockerコンテナのNuxtにlocalhostでアクセスできないとき

export default {
  server: {
    // port: 8000, // デフォルト: 3000
    host: '0.0.0.0', // デフォルト: localhost,
    // timing: false
  }
}

上記のhostの設定でlocalhostから0.0.0.0にしたらアクセス可能になる。

なお、npm run devのサーバーは開発用で、負荷には耐えられない。複数タブを開いたままにしたりすると、状態の監視などで負荷が高い。動作が遅いと思ったら、タブを閉じた方がいい。

Concept

静的サイト生成
About

Nuxt 2 - 静的サイト生成

Static Site Generation (SSG)。

npm run generateでdist以下に静的データが出力される。これをpublicなどに配置するとそのままフロント用のアプリサーバーなしで動作するというもの。

動的ルーティング

Nuxtjs 動的なルーティング静的ページ自動生成 | Hapicode

https://chatgpt.com/share/6886c2c2-b5cc-800b-8e03-e301c22d5eff

ユーザーページなど、動的ルーティングを含んでいる場合の静的ページ作成は2通りの方法がある。

nuxt.config.jsなどで設定する。

import axios from 'axios'

export default {
  generate: {
    routes: async () => {
      const { data: users } = await axios.get('https://example.com/api/users')
      return users.map(user => `/user/${user.id}`)
    }
  }
}

上記のように、静的サイト作成時に、動的ルーティングの前一覧を取得して、/user/1なども全部作成するというのが1個の方法。ただ、この方法だと後から追加された場合に対応できない。

export default {
  generate: {
    fallback: true  // ← これがポイント!
  }
}

generate.fallbackを指定するといいらしい。trueだと404.htmlになる。他はHTMLファイルのパス。

Directory Structure

pages

Nuxt 2 - ページディレクトリ

pagesに配置したvueファイルがそのまま公開される。

URLパスに、動的パラメーターがある場合、_から始まるファイル名にする。

_slug.vueファイルにしたら、params.slugで値にアクセスできる。

<template>
  <h1>{{ this.slug }}</h1>
</template>

<script>
  export default {
    async asyncData({ params }) {
      const slug = params.slug // "/abc" パスにアクセスすると、slug は "abc" になります。
      return { slug }
    }
  }
</script>
nuxt.config.js

Nuxt 2 - Nuxt 設定ファイル

Nuxtの設定。

generate: HTMLファイル変換時のパラメーターを設定。dir=distの代わりの配置場所。


axios

Introduction - Axios Module

Frontendの有名HTTPクライアントライブラリー。

Setup

Setup - Axios Module

@nuxtjs/axiosを依存関係に追加する。

npm install @nuxtjs/axios

nuxt.config.jsにmodulesで追加する。

export default {
  modules: ['@nuxtjs/axios']
}

これを指定すると、$axiosでNuxt内で参照可能になる。

Other

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

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() } }) }


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