オートコンプリート UI 用ライブラリ ac-box 作った
シンプルなオートコンプリートコンボボックス UI ライブラリ ac-box を作った。
特徴
- 他の大きなライブラリに依存しない。スタンドアロン版ミニファイ済み 9KB。
- UI の位置は自動で fix される。ボーダや背景などの装飾部分は独自に定義する。
使い方
README を参照してください。
Typeahead とか jQuery UI とかあるけど?
jQuery に依存したくない。
〜する機能はないの?
issue にもらえれば対応できるかもしれないです。
実装の話
オートコンプリートは、シンプルな機能ながらも微妙に細かなインタラクションがあるため、UI 実装の練習になる。
Flux
Flux は、MVC アーキテクチャを Observer Pattern + Command Pattern + データフローの一方向性という制約のもと実現しようというアーキテクチャ。「Flux」という、あえて「MVC」を連想させない名前を付けることで、最近の MVC 戦争に巻き込まれるのを微妙に回避した感があり、初学者にとっても分かりやすいのではないか。
View ステートとレンダラーによる描画
大体次のような感じ。
- イベントハンドラ内で、次のような View ステートのプロパティを更新する
this.state = { // オートコンプリートメニュー開閉状態。 // input にフォーカスあたったら true にしたりする isOpen: null, // input に設定する値。ESC でメニュー選択を中断したいので、 // DOM 要素のプロパティ外にステートとして保持する value: null, // オートコンプリートメニュー内でフォーカスがあたっている // インデックスを表す数字。UP とか DOWN でインクリ / デクリメントする focusedIndex: null }
このライブラリでは使ってないけど、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 は
- node_modules は
これらの依存関係の解決方法を 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 シェル)
を使えば近づけることはできる。
※ 一番下にチュートリアル的なサンプルへのリンクがあります。
何が問題?
Ruby や Python、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_OPTS
で JVM オプションを渡すことになる。
% MAVEN_OPTS="-javaagent:<pathTo>/springloaded-{VERSION}.jar -noverify" mvn tomcat7:run
これで、クラスをコンパイルすれば再起動なしに即反映されるアプリケーションサーバ環境が手に入った。試しに Controller クラスの @RequestMapping
の value
値を書き換えてコンパイルすると、アノテーションの変更であっても即座反映されることが確認できる (ただし 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。
ひな形は次の方法で作ることができる。
- SPRING INITIALIZR (オンラインジェネレータ)
- Spring Tool Suite (STS) のメニューで File > new > Spring Starter Project
- Spring CLI をインストールして
% spring init -x <project_name>
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のバージョンで発生するかは不確か。