オートコンプリート UI 用ライブラリ ac-box 作った

シンプルなオートコンプリートコンボボックス UI ライブラリ ac-box を作った。

デモはこちら

特徴

  • 他の大きなライブラリに依存しない。スタンドアロン版ミニファイ済み 9KB。
  • UI の位置は自動で fix される。ボーダや背景などの装飾部分は独自に定義する。

使い方

README を参照してください。

Typeahead とか jQuery UI とかあるけど?

jQuery に依存したくない。

〜する機能はないの?

issue にもらえれば対応できるかもしれないです。

実装の話

オートコンプリートは、シンプルな機能ながらも微妙に細かなインタラクションがあるため、UI 実装の練習になる。

Flux

Flux アーキテクチャを実装している。

Flux は、MVC アーキテクチャを Observer Pattern + Command Pattern + データフローの一方向性という制約のもと実現しようというアーキテクチャ。「Flux」という、あえて「MVC」を連想させない名前を付けることで、最近の MVC 戦争に巻き込まれるのを微妙に回避した感があり、初学者にとっても分かりやすいのではないか。

View ステートとレンダラーによる描画

大体次のような感じ。

  1. イベントハンドラ内で、次のような View ステートのプロパティを更新する
this.state = {
  // オートコンプリートメニュー開閉状態。
  // input にフォーカスあたったら true にしたりする
  isOpen: null,
  // input に設定する値。ESC でメニュー選択を中断したいので、
  // DOM 要素のプロパティ外にステートとして保持する
  value: null,
  // オートコンプリートメニュー内でフォーカスがあたっている
  // インデックスを表す数字。UP とか DOWN でインクリ / デクリメントする
  focusedIndex: null
}
  1. その後レンダリング関数をコール。レンダリング関数は View ステートを参照して DOM 要素を更新する

このライブラリでは使ってないけど、React を使えばここらへんの仕組みをパフォーマンスを失うことなく自動的にやってくれる。

Grunt や gulp のかわりに Make も使ってみよう

フロントエンド開発のタスクランナーとして Grunt や gulp、npm run-script なんかを使ってきたが、今は Make を使っている。フロントエンド分野ではあまり馴染みのないツールかもしれないが、必要十分な機能性と高い表現力のバランスの良さを実感し、一巡辿ってゴールにたどり着いた感がある。もっと流行ってほしい。

Make は Makefile に定義したルールにしたがってビルドプロセスを実行する。しかし Makefile には独特な表現が多く、$@ とかのマクロはググりようがなくてちょっとしんどい。とはいえ、いきなり高度な使い方をしようと思わなければ簡単なので、以下を参考に導入してみてほしい。

STEP 1. コマンドのエイリアスを書く

基本はただのエイリアスです。

JavaScript をビルドする例

次の Makefile があるディレクトリで make build コマンドを実行すれば Browserify でのビルドが実行される。

build:
    node_modules/.bin/browserify src/main.js -o dist/bundle.js

なお build というタスク名的な部分のことを ターゲット という。

ミニファイしたければ Uglify にパイプ、もしくは uglifyify トランスフォームすればいい。

build:
    node_modules/.bin/browserify src/main.js | \
    node_modules/.bin/uglifyjs -o dist/bundle.js

ターゲットの部分には生成したいファイル名、例えば dist/bundle.js などを指定できる。そうした場合の Makefile は以下のように、コマンドは make dist/bundle.js となり、自然言語的に理解しやすくなる。

dist/bundle.js:
    @node_modules/.bin/browserify src/main.js | \
    node_modules/.bin/uglifyjs -o dist/bundle.js

なおコマンド部のはじめに @ をつけてみたが、これは実行するコマンドを標準出力しないための記号。コマンドを確認したければつけないままで OK。

JavaScript をインクリメンタルビルドする例

watchify がそれ自身でインクリメンタルビルドする機能を持っているので、これを叩くだけ。

watch-js:
    @node_modules/.bin/watchify src/main.js -o src/bundle.js -v -d

STEP 2. 依存ターゲットを書く

上の Makefile は本当にただのエイリアスに過ぎないので、次は 依存ターゲット を導入する。

dist/bundle.js を生成するまでに必要な依存関係をリスト化してみると、

  • dist/bundle.js は
    • browserifyuglifyjs コマンドが使用できなければ出力できない。つまり Browserify や Uglify などの Node モジュールが格納されている node_modules ディレクトリの存在に依存 している。
    • dist/bundle.js は、 dist ディレクトリがないと出力できない。つまり dist ディレクトリの存在に依存 している。
  • node_modules は
    • package.json ファイルを元に npm install コマンドによって作られる。つまり package.json ファイルの存在 に依存している。

これらの依存関係の解決方法を Makefile で表現すると次のようになる。

dist/bundle.js: node_modules dist
    @node_modules/.bin/browserify src/main.js | \
    node_modules/.bin/uglifyjs -o dist/bundle.js

dist:
    @mkdir -p dist

node_modules: package.json
    @npm install

このとき make dist/bundle.js を実行すると、必要な依存を自動的に解決してくれる。つまり、初めに node_modules の解決のため npm install が実行され、次に dist の解決のため mkdir -p dist が実行される。最後に dist/bundle.js が生成される。つまり make dist/bundle.js コマンド一つを実行すれば、他のコマンドを覚えたり実行せずにビルドできる。

しかもターゲットと依存ターゲットのファイルのタイムスタンプを比較し、更新が必要なければコマンドはスキップされる。例えば package.json よりも node_modules が新しければ、node_modules の更新は不要なので npm install は実行されない。

STEP 3. 他の継続プロセスを並列実行する

ウェブサーバ越しに動作・表示確認するために http-server を起動したいとする。さらに RESTful API モックサーバ json-server を起動したいとする。とりあえずそれぞれのプロセスの起動のためのターゲットを定義すると以下のようになる。

run-dev-server: node_modules
    @node_modules/.bin/http-server

run-api-mock-server: node_modules
    @node_modules/.bin/json-server --watch db.json

watch-js:
    @node_modules/.bin/watchify src/main.js -o src/bundle.js -v -d

これらのプロセスは並列に実行したいもの。そんな時は Make の j オプションでパラレル実行できるので、make -j run-dev-server run-api-mock-server watch-js のようなコマンドを実行するといい。とはいえこのコマンドを毎回叩くのは面倒なので、これを更に Makefile に定義しておく。

watch:
    @make -j run-dev-server run-api-mock-server watch-js

こうすれば、make watch すれば、3つのプロセスがパラレル実行される。仮にエラーでどれかのタスクが停止しても、Make のプロセスを止めればすべてのプロセスが止まるので、バックグラウンドでプロセスが残り続ける心配もない。npm run-script などで & 区切りで実行すると、バックグラウンドプロセスが残りやすいので、これは便利。

STEP 4. 変数とかマクロとか関数とか使う

ここまでの内容で十分に便利に使えるが、Makefile らしさを出すために以降では簡単なマクロを使う例を紹介する。ただししんどくなってきたら本末転倒なのでやめよう。

はじめのほうに書いた次の Makefile は、

dist/bundle.js:
    @node_modules/.bin/browserify src/main.js | \
    node_modules/.bin/uglifyjs -o dist/bundle.js

次の様に書き換えることができる。

SRC       = src
DIST      = dist
MAIN_JS   = $(SRC)/main.js
BUNDLE_JS = $(DIST)/bundle.js

$(BUNDLE_JS):
    @node_modules/.bin/browserify $(MAIN_JS) | \
    node_modules/.bin/uglifyjs -o $@

変数定義 VAR=foo と参照 $(VAR)、更にターゲット名を表すマクロ $@ を導入した。

(番外) STEP 5. Windows 対応

Windows よくわからないけど、MinGW とか使ってほしい。コマンドプロンプトでは諦めてほしい。こんな感じで /\ に置換する関数を使うといけたりもする。諦めてほしい

ifdef SystemRoot
    fixPath = $(subst /,\,$1)
else
    fixPath = $1
endif

$(call fixPath,dist/bundle.js):
    @$(call fixPath,node_modules/.bin/browserify src/main.js -o dist/bundle.js)

Grunt, gulp, npm run-script との比較

  • Grunt 遅い、タスク定義が面倒。
  • gulp 早いけどタスク定義が面倒。
  • Grunt も gulp も、プラグイン化が必要。バージョンアップ追従のタイムラグや、中にはメンテナンスされなくなるものもあるので、プラグイン化されたものではなく生で使えるに越したことはない。
  • npm run-script は表現力が足りない。簡単なタスクならいいが、マルチラインやコメントが書けないので複雑なタスクは無理。
  • シェルスクリプトもいいけど、一貫したプラクティスを提供する Make のほうが乗っかりやすい。

サンプル

https://github.com/keik/frontend-with-make

git clone https://github.com/keik/frontend-with-make.git して make すれば依存パッケージのインストールやら何やらができるはず。Makefile 活用例のサンプルなので、アプリ部分のショボさは無視してください。

SlickGrid のヘッダ行をグループ化する slickgrid-colgroup-plugin 作った

SlickGrid のヘッダをグループ化するプラグイン slickgrid-colgroup-plugin を作ったので、ギョームシステムの開発とかにどうぞ。MIT。

デモページ にいくつかのサンプルを置いてます。

SlickGrid 自体についても簡単に紹介すると、大量のデータをグリッドで表示できることを特徴としたフレームワークで、その仕組としてはスクロールされるたびに表示領域の DOM 要素だけを動的に作り直すことによって大量データを少ない DOM 要素数で表現している。

プラグイン v1.0.4 における使い方は、SlickGrid オブジェクトにプラグインを登録して

grid.registerPlugin(new Slick.Plugins.ColGroup());

カラム定義で children プロパティを用いてグループ構造を持ったカラム定義を与えるだけ。

var columns = [
  {id: 'col1', name: 'col 1', children: [
    {id: 'col1-1', name: 'col 1-1', field: 'col1-1'},
    {id: 'col1-2', name: 'col 1-2', field: 'col1-2'}
  ]},
  {id: 'col2', name: 'col 2', children: [
    {id: 'col2-1', name: 'col 2-1', field: 'col2-1'},
    {id: 'col2-2', name: 'col 2-2', children: [
      {id: 'col2-2-1', name: 'col 2-2-1', field: 'col2-2-1'},
      {id: 'col2-2-2', name: 'col 2-2-2', field: 'col2-2-2'}
    ]}
  ]}
];

LL言語並の速度でJavaウェブアプリケーションを開発する

結論

無理かもしれないけど

Spring Boot + Spring Loaded + Thymeleaf (+ Groovy シェル)

を使えば近づけることはできる。

※ 一番下にチュートリアル的なサンプルへのリンクがあります。

何が問題?

RubyPython、Node なんかを使った場合と違って、Java を使ったウェブアプリケーション開発はとにかく「待ち時間」が長くないですか?

API の実際の動作を確認したい」「画面表示を確認したい」と思ったら、アプリケーションサーバの種類にもよるけど結果を確認するのに数十秒から数分はかかる…… ゆっくり仕事していいよ というお告げを聞いているかのようですね。

そんなときに使いたいツールが「Spring Loaded (Spring 前提) 」と「Thymeleaf」。

Spring Loaded is 何

クラスファイルが変更されたら、アプリケーションサーバにリロードさせるエージェント。 いわゆる Hot code replace よりも広範囲な変更を反映できるし、反映の速度も速い、本当に速い。

使い方は簡単で、Spring Loaded の JAR をダウンロードして、Java プロセス起動時に次のように JVM オプションに指定してやるだけ。

% java -javaagent:<pathTo>/springloaded-{VERSION}.jar -noverify SomeJavaClass

Maven で組み込み Tomcat を起動するような場合は MAVEN_OPTSJVM オプションを渡すことになる。

% MAVEN_OPTS="-javaagent:<pathTo>/springloaded-{VERSION}.jar -noverify" mvn tomcat7:run

これで、クラスをコンパイルすれば再起動なしに即反映されるアプリケーションサーバ環境が手に入った。試しに Controller クラスの @RequestMappingvalue 値を書き換えてコンパイルすると、アノテーションの変更であっても即座反映されることが確認できる (ただし Interface の変更とかはダメっぽい) 。

ゆっくり仕事している暇 has lost...

Thymeleaf is 何

テンプレートエンジン。特徴は、テンプレートを、そのままウェブブラウザで表示可能な HTML として書けること。

HTML・CSS なんて、一発でうまいこと表示できるものが書けることのほうが稀で、試行錯誤を繰り返して作るもの 。そのサイクルが、本来であればブラウザをリロードする1秒で済むところ、Java のせいで60秒になってたとしたら、人月60倍、100万円のシステムが6000万円に化ける。おいしいwwwww

Thymeleaf はそんな目に合う機会を大幅に減らしてくれる。

使い方の例として、本のリスト books : List<Book> を、次のような表組みで出力する場合、

# Title Author Publisher
1 Java言語で学ぶデザインパターン入門 結城浩 ソフトバンクパブリッシング
2 Spring3入門 長谷川裕一 ほか 技術評論社

Thymeleaf では次のように書ける。

<table>
  <thead>
    <tr>
      <th>#</th>
      <th>Title</th>
      <th>Author</th>
      <th>Publisher</th>
    </tr>
  </thead>
  <tbody th:remove="all-but-first">
    <tr th:each="book, itr : ${books}">
      <td th:text="${itr.index} + 1">1</td>
      <td th:text="${book.title}">Java言語で学ぶデザインパターン入門</td>
      <td th:text="${book.author}">結城浩</td>
      <td th:text="${book.publisher}">ソフトバンクパブリッシング</td>
    </tr>
    <tr>
      <td>2</td>
      <td>Spring3入門</td>
      <td>長谷川裕一 ほか</td>
      <td>技術評論社</td>
    </tr>
  </tbody>
</table>

th: プレフィックスで始まる属性が、Thymeleaf 用に使われるもの。th:each="book, itr : ${books}" によって、本の数だけ tr 要素と4つの td 子要素が繰り返し出力される。

注目すべき点は、レイアウトを作成するための画面モックに、いくつかの th: 属性を追加しただけであるということ。th: 属性はウェブブラウザでは解釈されない (invalid にはなるけど) のでレイアウトが崩れることはなく、画面モックとして役割も継続しつつテンプレートとしても動作する HTML が記述できる。さらに、th:remove="all-but-first" のような属性を使うと、「デプロイされたら最初の1つの子要素 (th:each が指定されている要素) だけ出力する」という制御が可能になる。

デザイナーとプログラマーの分業 has came true...

ついでに Spring Boot is 何

Java でウェブアプリケーションをつくろうと思ったとき、まずはじめにディレクトリのレイアウトを作って、依存モジュールを定義して、XML で設定を書いて……覚えてられないよ何があなたとJAVAだよ

そんなとき Spring Boot が便利なんですよ。

Spring Boot は、すぐに実行可能な Spring プロジェクトのひな形を作って、しかも依存モジュールの設定を自動的にしてくれる。例えば Thymeleaf がクラスパス上にあれば Thymeaf のテンプレート探索パス・キャッシュ有無・エンコーディングなどの設定が、HSQLDB がクラスパス上にあれば JDBC ドライバなどのデータソース設定が、自動的になされる*1

ひな形は次の方法で作ることができる。

2番目と3番目の方法は、内部的に1番目のジェネレータからひな形をダウンロードしてくるだけ。

ひな形が手に入ったら

% mvn spring-boot:run

で組み込み Tomcat が起動する。例によって Spring Loaded を有効にする場合は、こんな感じで起動するといい。

$MAVEN_OPTS="-javaagent:springloaded-1.2.1.RELEASE.jar -noverify" mvn spring-boot:run

http://localhost:8080 にアクセスしても、はじめは Controller を実装していないので404画面しかでない。Spring Loaded でメキメキ実装しけいけばいい。

チュートリアル的なサンプル

https://github.com/keik/spring-boot-tutorial

コミット順に上から並べています。

*1:内部的には、@Conditional というアノテーションを使って、クラスパス上にあるモジュールに応じて読み込まれる設定クラスを変える仕組みになっている

Cordovaプラグインでアクセスポイントの情報を取れるようにした

Apache Cordovaが公式的にメンテしているCordovaプラグイン Network Information (Connection) を拡張して、Wi-Fi接続時のアクセスポイントの情報を取得できるようにした。ただしAndroidに限る。

https://github.com/keik/cordova-plugin-network-information/tree/add-wifi-info-on-android

もともとこのプラグインは、現在のネットワーク接続の種別(Wi-Fiとか、LTEとか)だけが取得できる。

console.log(navigator.connection.type); // -> "wifi"

機能拡張によって、アクセスポイント情報を次のようにして取得できるようにした。

console.log(navigator.connection.wifi); // -> { ssid: '<your-ssid>', ipaddress: '<your-ipaddress>', macaddress: '<your-macaddress>', networkid: '<your-networkid>' }

なお、このプラグインAPI設計は、W3Cで策定予定だった The Network Information API に基づいているが、現在この仕様の策定状況は Retired になっている。なのであんまり細かいことを気にせず、適当に wifi とかいうプロパティにWi-Fiアクセスポイント情報を突っ込んだ。

(所感) Cordova本当に流行(って)るんすか?

Cordovaはプラットフォームの差分を "プラグイン" という形式で部分的に吸収するためのフレームワークにすぎないので、プラグインが豊富であればあるほどマルチプラットフォーム対応が容易になる。

じゃあそのプラグインはどれだけあるのか、と PLUGINS REGISTRY を見ると、まず登録されているプラグイン数は現在のところ281個とまずまず多い。しかし、これらのうち結構な割合で、メンテされてなさそう、または特定プラットフォームのみサポートするプラグインだったりして、様々な要求にどれだけ答えられるのかは微妙なところ。

こうした状況は、少なくとも個人でマルチプラットフォーム対応のプラグインを開発するハードルの高さが原因にあるんじゃないかと思う。プラットフォームごとの開発環境を準備するのも、開発スキルを習熟するのも、どちらも結構負担が大きい。プラグイン開発の面白みも、アプリを作るわけじゃないので、あまりない。

それと、Cordovaで救済されたい人ってのは、個人開発者よりも、顧客からマルチプラットフォーム対応を求められるエンタープライズの人たちなんじゃないかな。そういう背景もあって、Monacaのようなサービスは独自に開発したプラグインOSSとせず、サービスの価値を高めるための商売道具にしているんじゃないかな。


エスアイアーの心を掴んで離さない、コルドバ

jQueryの .text() が遅かった

DOM要素内の文字列を変更する処理にjQuery.html() を使用しているコードを見て、「ムププw .text() のほうが速いからw」とか煽ってたら、実は .html() のほうが速かったという悲しい事件が起きた。

結論から言うと、jQuery 1.xの場合はIE6, 7, 8サポートのために .text() が遅く、 jQuery 2.xの場合は .text() のほうが速い。

DOMなら textContent が速いよ

jQueryの前にまず、素のDOMを使ったコードでは、 Element.innerHTML プロパティに文字列を代入するパターンをよく見かける。 innerHTML

el.innerHTML = '<p>チャーハン</p>';

のようにすれば勝手にP要素を作ってくれるので大変便利ではあるが、HTML文字列を含まない文字列についてもパース処理が実行されるので遅い。

もし書き換える文字列からHTML要素を生成する必要がないなら、Element.textContent を使用して

el.textContent = 'カレー';

のようにしたほうが断然速い。

そんな方法で文字列を10,000回書き換える処理時間を計測するサンプルだよ。

DOM

jQuery 1.xなら .html() が速いよ

同じ関係がjQuery.html().text() にも成り立つだろうと思っていたが、実際には .html() のほうが速かった。

ソースコードを確認したら、jQuery 1.xではIE6, 7, 8サポートのため、 .text() の実装に Element.textContent を使わず HTMLDocument.createTextNode() を使用していた(該当箇所)。このAPIが文字列のノードを作るためだけのAPIのくせにイマイチ遅いからファックだ。その結果 .text()のほうがむしろ遅いという虚しい結果に。

jQuery 1.11.1

jQuery 2.xなら .text() が速いよ

jQuery 1.xに対して、2.xではIEサポートを打ち切って Element.textContent を使用している(該当箇所)。よって .text() のほうがちゃんと速くなってる。

jQuery 2.1.1

結論

DOMで書け(雑)


(付録) 時間測定コード

var $targetArea = $('#target-area');

var start = +new Date();

for (var i = 10000; i--; ) {
  // それぞれの方法
  //   $targetArea.html('foo');
  //   $targetArea.text('foo');
  //   $targetArea[0].innerHTML = 'foo';
  //   $targetArea.[0].textContent = 'foo';
}

setTimeout(function () {
  var end = +new Date();
  resultEl.textContent = end - start + 'ms';
});

MODxで「無効なチャンク名です」

環境
FreeBSD 10.0-RELEASE-p4
PHP 5.4.23
MODX Revolution 2.2.10-pl (traditional)

チャンクやスニペットが保存できない、または拡張のインストールが失敗するといった現象が起きたら、 PHPのログに以下のようなエラーが出ていないか確認する。

PHP warning: preg_match(): Compilation failed: invalid range in character class at offset 38

エラーの原因と解決方法は次のスレッドの通り。 http://forums.modx.com/thread/89848/solved-problem-with-formit-2-2-0-php-warning-preg-match

クソ意訳すると、正規表現の "-" 文字の扱いがPHPバージョンによって異なることが原因のため、

change in the following files : 

core/model/modx/mysql/modsnippet.map.inc.php
core/model/modx/mysql/modchunk.map.inc.php

this rule => '/^(quergo!\s)[a-zA-Z0-9\x2d-\x2f\x7f-\xff_-\s]+(quergo!\s)$/' by 
this rule => '/^(quergo!\s)[a-zA-Z0-9\x2d-\x2f\x7f-\xff-_\s]+(quergo!\s)$/

のように正規表現を変更するか、またはPHPのバージョンを変えればOKなんだって。

どのPHPのバージョンで発生するかは不確か。