「JavaScript library」の版間の差分
(Handlebars Introduction) |
(Pakage manager NPM Install idealTree:lib: sill idealTree buildDeps) |
||
| (同じ利用者による、間の17版が非表示) | |||
| 1行目: | 1行目: | ||
== Grunt == | [[Category:JavaScript]] | ||
== Tool == | |||
=== Grunt === | |||
=== About === | ==== About ==== | ||
JavaScriptのタスクランナー。ビルドやファイルの整形、変換などを行ってくれる。 | JavaScriptのタスクランナー。ビルドやファイルの整形、変換などを行ってくれる。 | ||
| 8行目: | 10行目: | ||
[https://gruntjs.com/ Grunt: The JavaScript Task Runner] | [https://gruntjs.com/ Grunt: The JavaScript Task Runner] | ||
==== grunt vs. gulp ==== | ===== grunt vs. gulp ===== | ||
[https://ics.media/entry/3290/ 絶対つまずかないGulp 5入門 - インストールとSassを使うまでの手順 - ICS MEDIA] | [https://ics.media/entry/3290/ 絶対つまずかないGulp 5入門 - インストールとSassを使うまでの手順 - ICS MEDIA] | ||
| 16行目: | 18行目: | ||
* gulp: JavaScriptで非同期。 | * gulp: JavaScriptで非同期。 | ||
=== Gruntfile === | ==== Gruntfile ==== | ||
Gruntfile.jsかGruntfile.coffeがタスクの定義ファイル。 | Gruntfile.jsかGruntfile.coffeがタスクの定義ファイル。 | ||
以下のコマンドでgruntで実行可能なタスク一覧が表示される。 | 以下のコマンドでgruntで実行可能なタスク一覧が表示される。 | ||
grunt --help | |||
単にgruntとだけ実行すると、defaultのタスクが実行される。基本はサブコマンドを指定する。 | 単にgruntとだけ実行すると、defaultのタスクが実行される。基本はサブコマンドを指定する。 | ||
== Node.js == | === Node.js === | ||
=== Install === | ==== Install ==== | ||
[https://nodejs.org/en/download/package-manager Node.js — Download Node.js®] | [https://nodejs.org/en/download/package-manager Node.js — Download Node.js®] | ||
| 31行目: | 33行目: | ||
Linuxの場合、以下。 | 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に以下の内容が追記される。即座にインストールを反映したければ現在の端末でも実行する。 | 最初の行を実行してインストールしたら~/.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] | |||
== jQuery == | ====== 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|栃木県佐野市] | ||
| 61行目: | 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 | |||
*/ | */ | ||
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(); | |||
} | |||
) | |||
} | |||
「[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 === | ==== DataTables ==== | ||
jqueryのプラグイン。テーブル処理をうまくやってくれる。 | jqueryのプラグイン。テーブル処理をうまくやってくれる。 | ||
==== Option ==== | ===== Option ===== | ||
* [https://legacy.datatables.net/ref.html DataTables - Reference] | * [https://legacy.datatables.net/ref.html DataTables - Reference] | ||
* [https://datatables.net/reference/option/ Options] | * [https://datatables.net/reference/option/ Options] | ||
==== Old ==== | ===== Old ===== | ||
[https://legacy.datatables.net/index-2.html DataTables (table plug-in for jQuery)] | [https://legacy.datatables.net/index-2.html DataTables (table plug-in for jQuery)] | ||
v1.9以下の古いバージョンのサイトはこちらにある。 | v1.9以下の古いバージョンのサイトはこちらにある。 | ||
==== ServerSide ==== | ===== ServerSide ===== | ||
[https://datatables.net/manual/server-side Server-side processing] | [https://datatables.net/manual/server-side Server-side processing] | ||
通常はクライアント画面のみ。全データを検索したい場合、serverSideオプションが必要。ただし、このオプションはdatatablesのv1.10以上じゃない。 | 通常はクライアント画面のみ。全データを検索したい場合、serverSideオプションが必要。ただし、このオプションはdatatablesのv1.10以上じゃない。 | ||
==== Pagination ==== | ===== Pagination ===== | ||
ページ表示件数に関するオプションがいくつかある。 | ページ表示件数に関するオプションがいくつかある。 | ||
| 127行目: | 355行目: | ||
* lengthMenu/aLengthMenu: リストボックスの項目。デフォルトは[10, 25, 50, 100]。-1 ("All") で全表示。 | * 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] | |||
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:// | [https://v2.vuejs.org/v2/guide/forms Form Input Bindings — Vue.js] | ||
=== | ====== Basic Usage ====== | ||
v-model指令を、formのinput/textarea/select要素の双方向のデータ束縛に使える。 | |||
==== API ==== | |||
===== Options / Data ===== | |||
データ関係のプロパティーがある。 | |||
[https://v2.vuejs.org/v2/api/ API — 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 | |||
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 | |||
}}</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> | |||
<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 { | |||
props: { | |||
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行目: | 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 | |||
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) }}</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> | |||
<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 { | |||
props: { | |||
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 }, | ||
| 318行目: | 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> | ||
| 330行目: | 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] | ||
=== 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 == | === Handlebars === | ||
* [https://anken-hyouban.com/blog/2021/06/09/handlebars-js/ Handlebars.jsを理解する!初心者でも分かる特徴、Mustacheとの違い、基礎知識などを簡単に解説! | 案件評判] | * [https://anken-hyouban.com/blog/2021/06/09/handlebars-js/ Handlebars.jsを理解する!初心者でも分かる特徴、Mustacheとの違い、基礎知識などを簡単に解説! | 案件評判] | ||
| 348行目: | 756行目: | ||
他に有名なテンプレートエンジンに、Mustacheというのがあるが、Handlebars.jsはそれより、高速で上位互換とのこと。 | 他に有名なテンプレートエンジンに、Mustacheというのがあるが、Handlebars.jsはそれより、高速で上位互換とのこと。 | ||
=== Introduction === | ==== Introduction ==== | ||
[https://handlebarsjs.com/guide/#what-is-handlebars Introduction | Handlebars] | [https://handlebarsjs.com/guide/#what-is-handlebars Introduction | Handlebars] | ||
==== What is Handlebars? ==== | ===== What is Handlebars? ===== | ||
以下のような式で、オブジェクトからHTMLを生成するテンプレートエンジン。 | 以下のような式で、オブジェクトからHTMLを生成するテンプレートエンジン。 | ||
<nowiki><p>{{firstname}}</nowiki> <nowiki>{{lastname}}</nowiki><nowiki></p></nowiki> | |||
なお、この二重波括弧はマスタッシュ記法と読んだりする。これは、ヒゲに似ているからで、他にMustache.jsというテンプレートエンジンの名前から。 | なお、この二重波括弧はマスタッシュ記法と読んだりする。これは、ヒゲに似ているからで、他にMustache.jsというテンプレートエンジンの名前から。 | ||
==== Language features ==== | ===== Language features ===== | ||
===== Simple expressions ===== | ====== Simple expressions ====== | ||
<nowiki><p>{{firstname}}</nowiki> <nowiki>{{lastname}}</nowiki><nowiki></p></nowiki> | |||
{ | |||
firstname: "Yehuda", | |||
lastname: "Katz", | |||
} | |||
<nowiki><p>Yehuda Katz</p></nowiki> | |||
上記のように、マスタッシュ記法がJavaScriptのオブジェクトに変換される。 | 上記のように、マスタッシュ記法がJavaScriptのオブジェクトに変換される。 | ||
===== Nested input objects ===== | ====== Nested input objects ====== | ||
オブジェクトや配列もJavaScriptで参照できる。 | オブジェクトや配列もJavaScriptで参照できる。 | ||
<nowiki><p>{{person.firstname}}</nowiki> <nowiki>{{person.lastname}}</nowiki><nowiki></p></nowiki> | |||
{ | |||
person: { | |||
firstname: "Yehuda", | |||
lastname: "Katz", | |||
}, | |||
} | |||
===== Evaluation context ===== | ====== Evaluation context ====== | ||
組み込みのブロックヘルパーのeachとwithは、コンテキストを評価できる。 | 組み込みのブロックヘルパーのeachとwithは、コンテキストを評価できる。 | ||
withヘルパーはオブジェクトプロパティーのオブジェクトの前置を省略できる。 | withヘルパーはオブジェクトプロパティーのオブジェクトの前置を省略できる。 | ||
<nowiki>{{#with person}}</nowiki> | |||
<nowiki>{{firstname}}</nowiki> <nowiki>{{lastname}}</nowiki> | |||
<nowiki>{{/with}}</nowiki> | |||
{ | |||
person: { | |||
firstname: "Yehuda", | |||
lastname: "Katz", | |||
}, | |||
} | |||
eachヘルパーは配列。 | 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: [ | people: [ | ||
| 409行目: | 888行目: | ||
} | } | ||
<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
ここでプラットフォーム別のインストール方法がある。
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
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.IndexRouteApp.IndexController- the
indextemplate
上記になる。
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
アプリで、ユーザーがアプリの状態を変更するコントローラーを操作できるようにする方法が必要になる。例えば、ボタンを押したら、処理を行うような感じ。
コントローラーの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はもう古い!?これから・・・ | YORIYORK|栃木県佐野市
- jQueryとは何なのか? なぜ使わなくても(あるいは使わないほうが)いいのか? #JavaScript - Qiita
- そろそろなぜjQueryを使うのが難しいのかをちゃんとまとめようと思う。|榊原昌彦
- jQueryを終了する時が来ましたか?
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
通常はクライアント画面のみ。全データを検索したい場合、serverSideオプションが必要。ただし、このオプションはdatatablesのv1.10以上じゃない。
Pagination
ページ表示件数に関するオプションがいくつかある。
- pageLength/iDisplayLength: 1ページあたりの表示件数。デフォルトは10。
- lengthMenu/aLengthMenu: リストボックスの項目。デフォルトは[10, 25, 50, 100]。-1 ("All") で全表示。
Vue.js
Essentials
Template Syntax
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
v-forで反復可能。
<ul id="example-1">
<li v-for="item in items" :key="item.message">
{{ item.message }}
</li>
</ul>
:keyを指定すると、並べ替えなどでVueが識別可能になる。
Form Input Bindings
Basic Usage
v-model指令を、formのinput/textarea/select要素の双方向のデータ束縛に使える。
API
Options / Data
データ関係のプロパティーがある。
- 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
Install
Installation · Get Started with Nuxt v4
インストール方法がいろいろあって面倒くさい。
v2系は以下のコマンドでインストールするらしい。
npx create-nuxt-app@3 frontend
Commands
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
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
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の設定。
generate: HTMLファイル変換時のパラメーターを設定。dir=distの代わりの配置場所。
axios
Frontendの有名HTTPクライアントライブラリー。
Setup
@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
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
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
#if
引数の変数がfalse, undefined, null, "", 0, or []以外ならtrueと評価。
この#ifは値を渡すことしかできず、式を一切使えない。反転したい場合は、#unlessを指定する。
あるいは、カスタムヘルパー関数を作る。
#unless
#each
eachを使うことで、リストを反復できる。ブロック内で、thisを要素の参照に使える。
{
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内で要素がオブジェクトの場合、this.を省略して、いきなりプロパティーにアクセスできる (https://chatgpt.com/c/67e50098-cbd4-800b-89f5-501b03f4d6d2)。
変数名が、外と衝突する恐れがあるので、thisを明示した方が安全に感じる。
