「最新SPA開発を学ぼう!ウェブエンジニアのためのAngularJS入門」という題でPHPカンファレンス2014で登壇してきた
10月11日に開催されたPHPカンファレンス2014にて、「最新SPA開発を学ぼう!ウェブエンジニアのためのAngularJS入門」という題で登壇してきました。参加された皆さんお疲れ様でした。
正規表現ばかりに頼ってはいけない
文字列のパースをする必要がある時、どんな文字列にでも何でもかんでも正規表現で処理しようとするエンジニアをたまに見かける。
正規表現は確かに文字列を扱うための強力な手段だが、万能ではない。正規表現の性質上、そもそもパースできない文法があるからだ。従ってそういうケースの時には正規表現ではなく別の方法を使ったほうが良い。正規表現を無理やり使っても、バグを埋め込んだり、メンテナンスが難しかったり、正しく文字列をパース出来なかったりで良いことはあまりない。
正規表現がパースできない文字列
正規表現が苦手とする文法で一番よく言われるのは、再帰的な構文を含む文法である。例えば、括弧つきの数式なんかがそうで、1+1 でも (1+1) でも ( (1+1) ) でも ( ( (1+1) ) ) でも ( ( ( ( 1+1) ) ) ) でも、という風にいくらでも入れ子にできる。正規表現では、こういった文字列をパースしようにも括弧の対応を取ることができない(わからない人は実際試してみるといい)。
開発者の回りを見ると、括弧つきの数式以外にも再帰的な文法を持つ文字列はいくらでも転がっている。JSONだったりXMLだったりJavaScriptだったりS式だったり… あと正規表現の文法自体もサブパターンの括弧の対応を取れている必要があるので、正規表現は正規表現をパースできない。勿論、PCREの独自拡張で利用できる再帰的な文法のためのパターン(?R)があるけど、それはあくまで独自拡張である。
例え正規表現でパース可能な場合でも、正規表現が数十行から数百行に渡るような大規模な文法になれば、そんなものをメンテナンスしたいと思う人間はあまりいないだろう。
パーサコンビネータを使おう
無理やり正規表現を使う人がなぜ無理やり正規表現を使うかといえば、恐らくそれ以外のやり方を知らないからだろう。
それ以外のやり方というのは自分で一からパーサを書くのを含めていくつかあるけれども、自分はパーサコンビネータを使うことをお薦めする。
パーサコンビネータは、文字列をパースするための大変強力な手段である。正規表現よりも強力で、かつlex&yaccみたいな字句解析器と構文解析器を使うのよりも手軽だし、どの言語でもたいていパーサコンビネータのためのライブラリが存在する。
(次回へ続く)
新しいCSSの設計規約、AMCSSに関する個人的なまとめ
CSSの設計規約というと、BEMが有名ですが、最近またAMCSSという新しいCSSの設計規約が出てきました。この記事では、このAMCSSについて簡単に紹介したいと思います。
個人的なBEMの好きでない所
仕事でBEMをよく使っていて、優れた設計規約だとは思いつつも、使っていて気になる点がいくつかあります。BlockとElementとModifilerという3つの概念をクラス属性だけで表現しようとするため、非常に記法が見難いのと冗長なところです。
例えば、fooブロックのbarエレメントのhogeモディファイヤーを表現すると、以下のようになります。
<div class="foo foo--hoge"> <div class="foo__bar foo--hoge__bar"> ... </div> </div>
"__"や"--"という文字を区切りに使っているため、非常に冗長に見えます。また、初めてこの記法を見た人からはたいてい気持ち悪いと言われてしまいます。
AMCSSは属性を使ってCSSを設計する
BEMやその他の多くのCSSの設計規約が、class属性を使ってスタイルを記述する方式を採用していますが、AMCSSではHTMLの属性(Attribute)を使ってCSSを設計します。AMCSSのAMとはAttribute Moduleの略です。
例えば、BEMでボタンを表現しようとするときたいてい以下の様な形になります。
<button class="button">Button</button> <button class="button button--small">Small Button</button>
AMCSSの場合は、以下のようになります。
<button am-Button>Button</button> <button am-Button="small">Button</button>
スタイルの記述も、クラスのセレクタではなく属性セレクタを使って記述します。
[am-Button] { color: #999; border: 1px soild #ccc; } [am-Button~="small"] { font-size: 14px; }
概要を説明したところで、早速AMCSSの概念を説明してきます。
AMCSSの3つの概念
AMCSSには、BEMに似た以下の3つの概念があります。
- Module
- Variation
- Trait
上の3つの概念に従って開発者はCSSの記述を設計していくことになります。上から一つ一つ説明してきます。
Module
BEMにおけるBlockのような概念です。am-プレフィクスにアッパーキャメルでモジュール名を記述します。
<div am-Module> ... </div>
モジュールを記述した要素の子にスタイルを記述したい場合には、"-"ハイフンで区切って子モジュールを宣言します。この子モジュール名もアッパーキャメルで記述します。
<div am-MyModule> <div am-MyModule-Inner> ... </div> </div>
スタイルは、前述したように属性セレクタを使って記述します。
[am-MyModule] { ... } [am-MyModule-Inner] { ... }
Variation
Variationは、BEMのModifierに似た概念ですが、Moduleで宣言した属性の値として記述します。Moduleで宣言したスタイルを、上書きしたり拡張したりするために利用します。例えば、ボタンモジュールを少し拡張して、小さなボタンを作りたいときには、以下のようにsmallを属性値に指定してモジュールのバリエーションを作成できます。
<div am-MyButton="small"> ... </div>
スタイルは、属性セレクタと~=演算子を使って記述します。
[am-MyButton] { /* MyButtonモジュールのスタイルを記述 */ } [am-MyButton~="small"] { /* MyButtonモジュールのsmallバリエーションのスタイルを記述 */ }
バリエーションは、クラス属性の値と同様にスペース区切りで複数指定することも出来ます。
<div am-MyButton="small red">Button></div>
[am-MyButton~="small"] { ... } [am-MyButton~="red"] { ... }
Trait
Traitは、ある特定のスタイルに関するプリセットを提供します。
前述したModuleとは直交する概念であるため、単独で使うことも出来ますし、Moduleと組み合わせて使うことも出来ます。
Moduleと同様に属性を利用しますが、Trait名は小文字から始めてローワーキャメルで書きます。また、かならず属性と値の一対で宣言します。
<h1 am-typography="header">Title</h1> <button am-MyButton am-shape="rounded">Button</button>
[am-typography~="header"] { font-size: 30px; font-weight: 600; } [am-share~="rounded"] { border-radius: 8px; }
プレフィクス
AMCSSの文書では、説明のために属性名の最初に、"am-"というプレフィクスがついています。それに習ってこの記事でも属性のプレフィクスには"am-"を使っていますが、規約ではこの"am-"というプレフィクスは、短いものであればなにを使ってもかまいません。ValidなHTML属性を使いたい場合には、"data-"プレフィクスを使うことも可能です。
勿論、am-じゃなくてui-とかでも良いことになります。
[ui-MyButton] { ... }
気になる速度について
たまに、属性セレクタはクラスセレクタよりも遅いので使うべきではない、という話を聞くことが有ります。
確かに属性セレクタはクラスセレクタよりも数割遅いのですが、実際にウェブサイトを作っていて問題になることはほとんど無いでしょう。というのも、CSS自体がボトルネックになることはあまり無く、重たいネットワークや容量の最適化されていない画像、リソースなどのその他の処理の方が先にボトルネックになることはほとんどだからです。
また、CSSがボトルネックになる場合であっても、属性セレクタがボトルネックになるよりも前に、まず利用していないスタイルが多すぎることや、子孫セレクタの利用の方が先に問題になるでしょう。
どうしても速度が気になる場合には、通常のウェブサイトをAMCSS方式に変換してくれるAM Benchmarksというツールがあるので、それを使ってプロファイルを取ってみると良いでしょう。
終わりに
CSSを記述する際に、どういった設計規約を取るかによってCSSのメンテナンス性や拡張性は大きく変わります。この記事では、AMCSSという属性を使ってスタイルを記述するCSSの設計規約を紹介しました。
タスクランナーgulp.js最速入門
相変わらず仕事ではデザインやりつつJavaScript書いている。
タスクランナーとしてGrunt.jsを使っていたけれども、使ううちに段々不満がでてきた。遅かったり、記述が冗長になりがちでつらかったので最近になってgulpに乗り換えた。
gulpは良い。タスクは自動的に並列に実行され、かつストリームで処理されるので速いし、タスクの記述もストリームベースの書き方のおかげでGrunt.jsに比べるとだいぶ短くなる。
ただ、そこらにあるgulpをちょっと試しただけの日本語の記事やドキュメントをみてても実際のプロジェクトで使えるレベルまでの知識を得られず学習に一日かかった。
この記事では、gulpをまともに使えるようになるまでに必要な知識を書く。
導入とHelloWorld
まずは導入。npmからgulpをインストールする。
$ npm install gulp -g $ gulp -v [gulp] CLI version 3.5.6 [gulp] Local version 3.5.6
インストールできたら、以下のようなgulpfile.jsを置く。gulpfile.jsは、gulpコマンドを実行すると自動的に読み込まれるファイルで、Grunt.jsでいうGruntfile.jsである。
var gulp = require('gulp'); gulp.task('hoge', function() { console.log('HelloWorld!'); });
以下みたいにgulpコマンド叩くとタスクが実行される。
$ gulp hoge HelloWorld!
タスクを定義する
gulpでは、タスクが扱うデータはストリームによって処理される。例えば、lessファイルをコンパイルした後autoprefixerにかけるようなコードは、以下のようになる。
var gulp = require('gulp'); var less = require('gulp-less'); var autoprefix = require('gulp-autoprefixer'); gulp.task('css-compile', function() { return gulp.src('less/*.less') .pipe(less()) .pipe(autoprefix('last 1 version')) .pipe(gulp.dest('css/')); });
タスク定義の中で、gulp.src()で処理するファイルを指定して、gulp.dest('css/')で処理されたファイルが書き込まれる先を指定する。gulp.src()やgulp.dest()の詳細は、gulp API docsを参照する。
基本的にgulpそのものは何か特定の機能も持っているわけではない。ユーザはやりたいことに合わせてgulp-*パッケージをnpmでインストールしてから使うことになる。ここでは、gulp-lessとgulp-autoprefixerを使っている。
デフォルトで実行されるタスクを指定する場合にはGrunt.jsと同様にdefaultという名前のタスクを作るとよい。
タスクの定義には、次のように単に依存するタスクを複数指定することもできる。
gulp.task('default', ['foo', 'bar']);
これで、defaultタスクを実行するとfooタスクとbarタスクが実行される。
タスクの実行順を保証する
gulpでは、タスクの実行は自動的に並列化される。これはどういうことかといえば、以下のようなタスクを定義したとする。
gulp.task('default', ['foo', 'bar']);
この時、defaultタスクが実行されるとfooタスクとbarタスクが並列で実行される。その結果タスクの実行時間を短くしてくれる。
ただし状況によっては必ずしも並列で実行してほしくない場合がある。
例えば、生成したファイルを消すcleanタスクとファイルを生成するbuildタスクがあるとして、このふたつのタスクを実行する時にcleanタスクが終わってからbuildタスクが開始されないと意味が無い。
gulpでタスクの順序を指定する場合には、タスクを非同期化して、かつタスクの依存関係を設定する。
タスクを非同期化するには3つの方法がある。
- ストリームを返す
- コールバックを呼び出す
- プロミスを返す
1番簡単なのは、単にストリームを返すようにするやり方で、これは単にgulp.src()から生成したストリームを返せばそれで良い。
gulp.task('foo', function() { return gulp.src('src/*.js') .pipe(minify()) .pipe(gulp.dest('/build')); });
2番目のやり方は、タスクが終了したらその終了タイミングを伝えるコールバックを呼び出してやること。タスクを定義する仮引数を指定するとそこにコールバックが渡されるようになる。
gulp.task('foo', function(done) { setTimeout(function() { // タスク終了 done(); }, 1000); });
3番目は、タスク内でプロミスを返すやり方。個人的には使う機会がないので説明は省略。gulp API docsを参照する。
タスクを非同期化して、次にタスクの依存関係を追加すれば、タスクは順番通り実行されるようになる。タスクの依存関係を設定するには、gulp.task()メソッドの2番目の引数に依存するタスク名の配列を指定する。
以下の例では、fooタスクを実行しようとすると自動的にbarタスクが実行され、タスクが完了して初めてfooタスクが実行される。
gulp.task('bar', function() { return gulp.src('src/*.js') .pipe(minify()) .pipe(gulp.dest('/build')); }); // このタスクを実行する前にかならずbarタスクが実行される gulp.task('foo', ['bar'], function() { return gulp.src('./build/*.js') .pipe(doanything()) .pipe(gulp.dest('./build')); });
注意としては、単にタスクをまとめただけでは依存関係を設定したことにはならず、並行で実行される。以下のようなコードの場合、foobarタスクを実行しても、fooタスクとbarタスクは並列で実行される。
gulp.task('foobar', ['foo', 'bar']);
依存関係を設定せずに順序を指定したい場合には、次に記述するrun-sequenceを使う。
依存関係を設定する代わりにrun-sequenceを使う
タスクの実行順序を指定したいが、依存関係は設定したくない場合がある。
例えば、キャッシュファイルを消すclear-cacheタスクとファイルを生成するbuildタスクがあり、必ずしも毎回キャッシュを消したくはない場合があると仮定する。この時buildタスクにclear-cacheタスクを依存先に設定すると、buildタスクを実行されるたびに毎回キャッシュが消されてしまう。しかし依存関係を設定しなければ順序を保証できない。
こういう時には、run-sequenceパッケージを使うとよい。
var runSequence = require('run-sequence'); gulp.task('foobar', function() { runSequence('foo', 'bar'); });
foobarタスクを実行すると、fooタスクとbarタスクが順に実行される。run-sequneceパッケージを使うと依存関係を設定しなくてもタスクを順に実行できる。
タスク内のストリームをマージする
タスクの定義の中に複数のストリームがある場合がある。
gulp.task('foo', function() { // どうやって複数のストリームを返せば良い? gulp.src('src/*.coffee') .pipe(coffee()) .pipe(gulp.dest('js/')); gulp.src('src/*.less') .pipe(less()) .pipe(gulp.dest('css/')); });
タスクを非同期化するためにはストリームを返さなければならないが、ストリームが複数ある場合にはどうすればよいのだろう。
event-streamパッケージには、ストリームを一つにまとめるmergeメソッドがあるのでそれを使えばよい。
var merge = require('event-stream').merge; gulp.task('foo', function() { return merge( gulp.src('src/*.coffee') .pipe(coffee()) .pipe(gulp.dest('js/')), gulp.src('src/*.less') .pipe(less()) .pipe(gulp.dest('css/')) ); });
複数のストリームがある場合にはevent-streamパッケージのmergeメソッドを使ってストリームを一つにまとめるとよい。
タスク内のストリームの実行順を指定する
event-streamのmergeメソッドを使ってストリームを一つにまとめる場合にも、これらのストリームは並列で実行される。
タスク定義内で、複数のストリームに順序を付けて実行したい場合には、ストリームのイベントリスナを使う。
タスクを非同期化する場合には、タスクの完了コールバックも併せて使う。
// jsのコンパイルが終了してから、lessのコンパイルが行われる gulp.task('foo', function(done) { gulp.src('src/*.coffee') .pipe(coffee()) .pipe(gulp.dest('js/')) .on('end', function() { gulp.src('src/*.less') .pipe(less()) .pipe(gulp.dest('css/')) .on('end', done); // タスク完了 }); });
watchタスクを定義する
gulpには、ファイルの変更を検知してタスクの実行を行なうgulp.watch()が予め用意されている。
gulp.task('serve', function() { // src/js/*.jsファイルが変更されたら、build-jsタスクを実行する gulp.watch('src/js/*.js', ['build-js']); });
watch()メソッドの第二引数には、タスクの配列だけではなくコールバックも記述できる。
gulp.task('serve', function() { // src/js/*.jsファイルが変更されたら、build-jsタスクを実行する gulp.watch('src/js/*.js', function() { gulp.src('src/*.coffee') .pipe(coffee()) .pipe(gulp.dest('js/')); }); });
エラーが出てもコケないようにplumber使う
gulpではタスクの実行中に何かエラーが起こるとそのままタスクの実行が終了する。普通のタスクであればこれは問題ないが、watchするタスクの中でエラーが起きるとwatch自体も終了されてしまう。
そういった時には、エラーがおきても中断させないgulp-plumberを使える。
var plumber = require('gulp-plumber'); gulp.task('css', function() { gulp.src('src/*.less') .pipe(plumber()) // lessのコンパイルでコケても終了しない .pipe(less()) .pipe(gulp.dest('css/')) }); gulp.task('watch', function() { gulp.watch('src/js/*.less', ['css']); });
gulp-connectを使ってlivereloadする
ファイルが変更された場合にlivereloadしたい場合には、watchとgulp-connectを以下のように組合せて使う。
var gulp = require('gulp'); var connect = require('gulp-connect'); gulp.task('serve', ['connect'], function() { gulp.watch([ 'docroot/*.*' ]).on('change', function(changedFile) { // 変更がかかったファイルをconnect.reload()でライブリロードする gulp.src(changedFile.path).pipe(connect.reload()); }); }); gulp.task('connect', function() { connect.server({ root: [__dirname + '/docroot/'], port: 9001, livereload: true }); });
よく使うgulp-*パッケージ
その他、gulpを使う上で頻繁に使うパッケージを紹介しておく。
- gulp-header, gulp-footer - ストリームに何か文字列を付け加える
- gulp-concat - ストリームで扱うファイルを一つのファイルに連結する
- gulp-rename - ストリームで扱うファイル名を変更する
- gulp-clean - ファイルやディレクトリ消してくれる
- gulp-util - Grunt.jsで言うところのgrunt-util
終わりに
Grunt.jsに比べるとgulpの情報やリソースは充実していないが、一度慣れるとわざわざGrunt.jsを使う気がしなくなる。Grunt.jsに不満を感じ始めた時にはgulpを試してみると良いと思う、まる。
「HTML5でiOSアプリAndroidアプリを作ろう! 〜HTML5ハイブリッドアプリ開発入門〜」話しました
話しました。勉強会に参加した皆さんお疲れ様でした。
なぜ私はワイヤフレームをOmniGraffleで書くのか
仕事上、チームを組んでアプリやウェブアプリケーションを作る際には、ワイヤフレームというものを作成します。
ワイヤフレームは、これから具体的にどういった画面構成のものを作るのかということを関係者(ディレクター、開発者、デザイナー、プロマネ、クライアントなどなど)にきちんと共有するために作る非常に重要な成果物です。
ワイヤフレームには、全ての画面のおおまかなレイアウト、画面に表示する内容や文言、その後には画面間の遷移、アプリやウェブサイトが必要となるインタラクションなどを全て書き込みます。ワイヤフレームを書くと、これから作るものの画面構成・遷移・内容文言・レイアウト・インタラクションを関係者にレビューしてもらうことができます。フィードバックを経てワイヤフレームが承認されれば、この資料はそのままビジュアルデザイン時や実装時の仕様書の一つにもなります。
ワイヤフレームが具体的にどのようなフォーマットの資料になるかというと、以下のサイトに掲載されているワイヤフレームの例を見るとだいたい雰囲気わかると思います。
私は、このワイヤフレームをOmniGraffleという図形描画ツールを使って記述しています。OmniGraffle(オムニグラフと読みます)は、知っているひとは絶対に知っている図を書くのに最高に便利なツールで、ワイヤフレームに限らず仕事上の資料の多くはこのOmniGraffleを使って書いています。OmniGraffleにはStandard版とProfessional版の二種類があり、自分が使っているのは200ドルするProfessional版です。
このOmniGraffleでワイヤフレームを書いていると、Windowsでも編集できないかというふうなことを聞かれます。開発に用いる資料の一つなので自分でも編集したいという気持ちはわかりますが、残念ながらOmniGraffleのWindows版はありません。Omni Group社が出しているデスクトップアプリケーションはMaxOSXでしか利用できません。また、残念なことにMaxOSXを使っていても、OmniGraffleを購入するのにProfessional版だと200ドル、安いStanrdard版でも100ドル出さないと利用できません。100ドルという値段は、Adobe PhotoshopCS5などに比べると20倍以上安く、これだけ聞くとべらぼうに安い気がするのですが、無料で利用できるアプリが氾濫する今の世の中では控えめに見てもソフトウェアに100ドルも出すのはすこし抵抗があると思います。
図を作成するためのツールをウェブ上で検索してみるとわかるのですが、巷にはマシンにインストールする必要すら無くオンライン上で無料で利用できる図形描画ツールがたくさんあります。最も有名なのは、Nulab社のCacooや、Google Docsで提供されているGoogle Drawingなどだと思いますが、それ以外にもLucidchartやdraw.ioなどがあります。さらにワイヤフレームやプロトタイプを作成する専用のツールであればさらにもっと沢山のツールも紹介できます。
これらは手軽にオンライン上で使えるだけではなく、多くは無料かそれに近い値段で利用することができます。OmniGraffleのように、特定のプラットフォームでしか利用できないということはありません。ツールによってはオンライン上で利用できることを活かしてコラボーレションのための機能が備わっているものもあります。しかしそれにも関わらずなぜ私はこのOmniGraffleを使ってワイヤフレームを書くのでしょうか。前置きがかなり長大になりましたが、この記事ではその理由を説明します。
複数キャンバスに対応している
結構多くのオンラインツールで顕著なのは、複数キャンバスをサポートしていないことです。手軽な用途を想定しているのか、Google Drawingなどでは今開いている単位で複数のキャンバスを持つことしかできず、一枚の図しか作ることができません。
もし複数のキャンバスを使いたい場合には複数のファイルを作成する必要がありますが、これだとキャンバスを切り替えるのにわざわざ開きなおしたりしないといけません。1つの編集単位で図を沢山書きたい場合には、キャンバスを巨大にするしか方法がありません。
それなりに量を書く人にとってはこれは致命的です。ワイヤフレームの画面をたくさん書くための用途には使えないのです。
動作が軽い
OmniGraffleはMacOSXマシン上で動くアプリケーションです。オンライン上で利用できるツールに比べるとインストールする手間が増えるものの動作速度では圧倒的に軽いです。自分が使う上では、このことは大変重要です。
単に1-2時間少し使うぐらいではこのことことは、あまり気にならないのですが、自分が実際にワイヤフレームを書いている時には、何日間か続けて一日中ワイヤフレームをガリガリ大量に書くことになります。そういった時に少しでも動作が重かったりするとそれだけストレスになります。
共有レイヤ・変数に対応している
Cacooの設計思想では、ユーザのためにレイヤー機能はつけないポリシーになっているそうです。
OmniGraffleでは当然のようにレイヤはありますし、それ以外にも共有レイヤや変数などもサポートしています。共有レイヤとは、複数のキャンバス間で共通するレイヤを設定できる機能です。変数機能は、キャンバス番号やキャンバスの総数やその時の日付などの変数を埋め込める機能です。共有レイヤと変数機能を組み合わせると、例えば全てのキャンバスでページ番号を振ることができたりするので、きちんとした体裁の資料作成に便利です。ちなみに共有レイヤはProfessional版でしか利用できないので注意して下さい。
利用できるステンシルが豊富
OmniGraffleで利用できる図形は、全てステンシルの中から選択します。このステンシルは、OmniGraffleから利用できる図形をひとまとまりにグループ化したものです。OmniGraffleではこのステンシルが豊富にあります。自分自身でステンシルを作成することもできます。また、OmniGraffleのステンシルの共有サイト、Graffletopiaを見ると様々なステンシルが配布されており、ワイヤフレームを書く際に困ることがありません。
Illsutratorから図形をインポートできる
ステンシルの中に自分が利用したい図形がない場合には、自分で図形を作成することになります。素晴らしいことにOmniGraffleではAdobe Illustratorから図形を一瞬でインポートすることができます。
Illustratorで何らかの図形を作成して、選択した図形をOmniGraffle上にドラッグアンドドロップすると、その図形がそのままOmniGraffle上にインポートできます。ワイヤフレーム書いているとどうしてもステンシルに無い図形もあったりするのですが、この機能があるお陰でIllustratorで図形を作って簡単に利用できます。
スマートオブジェクトを利用できる
OmniGraffle上ではいわゆるPhotoshopで言うスマートオブジェクトが利用できます。スマートオブジェクトというのは、コピーしたものを編集してもそれがコピー元のオブジェクトにも反映されているようなオブジェクトのことです。ワイヤフレームを書いていると、各画面で共通する部品が出てくるので、スマートオブジェクトがあると修正する際に手間が省けて助かります。通常の図形描画ツールではスマートオブジェクトに対応していないため、コピーした部品を途中で修正する場合にはコピーしたものを全てひとつひとつ修正しなければいけません。
OmniGraffleでスマートオブジェクトを作成するには、オブジェクトを選択肢て「PDFとしてコピー」して貼り付けるとスマートオブジェクトになります。詳しくは次のページを参考にして下さい。
リンク機能でプロトタイプも作れる
自分ではあまり使っていないのですが、OmniGraffleではある図形をクリックした時には別の画面に遷移する、というような処理もできます。このことにより、単にワイヤフレームを書くだけではなく、画面遷移を確かめるためのアプリの簡単なプロトタイプも作成することもできます。OmniGraffleはHTML形式でもエクスポートできるので、作成したプロトタイプもブラウザさえあれば実行できます。
まとめ
- OmniGraffle便利最高
以上長々と書きましたが、ワイヤフレームをOmniGraffleで書く理由を紹介しました。
Angular.jsで組む場合のアーキテクチャは、MVCじゃなくてMVVMの方が良いっぽいと思った話
Angular.jsを何度か仕事で使ってみて、Angular.jsを使う場合のアーキテクチャはMVCじゃなくてMVVMにしたほうが良いなと思った話を書く。
Angular.jsをMVCフレームワークだと勘違いしていた
少しAngular.jsについて今まで勘違いしていたことがあって、Angular.jsではコントローラを定義できるのでてっきりMVCアーキテクチャで作るものとばかり思っていた。
公式ウェブサイトのタイトルをよくよく見てみると、「Superheroic JavaScript MVW Framework」と書いてある。MVWのWってなんだよとか思ってたらWhateverの略で、要するにMVCでもMVVMでもなんでも良いということらしい。
MVCで組んで困ったこと
勘違いが解ける前は、普通にMVCフレームワークとしてAngular.jsを使っていたけどもそれで何が困ったかというと、コントローラのコードが肥大して困った。
MVCでは、ビジネスロジックを扱うものをモデルとして、UI側のコードをビューとして、そしてモデルとビューを寄せ集めてくっつけるコントローラという風にアプリケーションのコードを3つに分割する。
それをAngular.jsで実際にやってみると、ビュー側で必要とする値とモデル側が管理する値が違ってくることが多く、その差異を埋め合わせるためのコードがコントローラでどんどん増えていって見通しが悪いものになりがちだった。
この傾向は、複雑なフォームやコンテキストに応じて変化するUIなど、インターフェイスがリッチになればなるほど強くなった。というのもリッチになればなるほどビュー側が欲しい値とモデル側が提供するデータの差異がさらに激しくなるからである。
MVVMって何よ
ModelとViewとViewModelに分けるアーキテクチャで、MVCのコントローラの代わりにビューモデルを用いる。ビューモデルとは、ビューを扱うためのモデルで、ビジネスロジックのために設計されたモデルと、実際に人間が触れるインターフェイス側のビューとの違いを吸収する。ビューモデルとビューとの通信は、通常データバインディングを用いて自動的に行われる。詳しくは、WikipediaのMVVMの記事を参照する。
ビューモデルでモデルとビューとの間を吸収するって、それってコントローラで記述するのと何が違うんだという話になるかもしれないが、ビューモデルはモデルと同様にクラスを書いてユニットテストを書きやすい状態で宣言する。クラスとして宣言することで再利用やモジュール化もしやすくなる。ユニットテストが書きづらく取り回しも効きづらいコントローラでアドホック的にモデルとビューの違いを吸収するよりかは、予めきちんと定義したビューモデルを作ってユニットテストを書きやすい状態にしておくほうがより良いと思う。
ビューモデルを定義したら、コントローラでは$scopeに対してそのビューモデルのインスタンスを割り当てるだけで終わり、というような状態にする。擬似コードだと次のような感じ。
angular.module('app').controller('FoobarController', function($scope, FoobarViewModel) { $scope.foobar = new FoobarViewModel(); });