JavaScript library

提供:senooken JP Wiki
2025年3月28日 (金) 16:59時点におけるSenooken (トーク | 投稿記録)による版 (Controllers init)

Tool

Grunt

About

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

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

Grunt: The JavaScript Task Runner

grunt vs. gulp

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

設定ファイルが違う。

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

Gruntfile

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

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

Node.js

Install

Node.js — Download Node.js®

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

Linuxの場合、以下。

  1. installs nvm (Node Version Manager)

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

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

nvm install 22

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

node -v # should print `v22.12.0`

  1. verifies the right npm version is in the environment

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

Framework

Ember.js

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

2011年に誕生。

Getting Started

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

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

基本的な構造。

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

How To Use The Guides - Getting Started - Ember Guides

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

Concepts

Naming Conventions

Naming Conventions - Concepts - Ember Guides

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

The Application

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

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

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

The Index Route

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

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

上記になる。

Templates

How To Use The Guides - Getting Started - Ember Guides

Handlebars Basics

How To Use The Guides - Getting Started - Ember Guides

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

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

Actions

Actions - Templates - Ember Guides

{{action}} helper

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

<!-- post.handlebars -->

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

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

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

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

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

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

Routing

Defining Your Routes

Defining Your Routes - Routing - Ember Guides

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

いくつかの方法がある。

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

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

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

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

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

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

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

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

こういう対応関係。

Dynamic Segments

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

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

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

Specifying a Route's Model

Defining Your Routes - Routing - Ember Guides

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

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

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

Controllers

Introduction - Controllers - Ember Guides

init

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

jQuery

jQuery API Documentation

レガシー

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

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

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

ajaxのasync/awaitでの書換

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

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

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

/**

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

DataTables

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

Option
Old

DataTables (table plug-in for jQuery)

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

ServerSide

Server-side processing

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

Pagination

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

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

Nuxt.js

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

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

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

//へのアクセスエラー

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

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

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

Vue.js

Naming

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

radioのbool

vuejs2 - Vue: Binding radio to boolean - Stack Overflow

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

IMEの入力制限

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

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

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

ハイライト

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

nextTickでやればいいか。

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

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

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

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

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



Template

Handlebars

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

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

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

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

Introduction

Introduction | Handlebars

What is Handlebars?

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

Language features
Simple expressions

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

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

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

Nested input objects

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

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

Evaluation context

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

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

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

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

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

Template comments

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

Block Helpers

Block Helpers | Handlebars

Simple Iterators

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

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

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

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

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

return ret; });

Built-in Helpers

Built-in Helpers | Handlebars

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