部分コンポーネント(2)
2015/11/20
前回、Cape.JS 1.3 の新機能である「部分コンポーネント」に関して概要を説明しました。
今回は、この仕組みを用いて「Cape.JS 入門」で作ってきた TODO アプリを書き換えていきます。
app/assets/javascripts/
ディレクトリに新規ファイル create_form.es6
を作成し、テキストエディタで次のような内容を書き込んでください。
class CreateForm extends Cape.Partial {
render(m) {
}
}
部分コンポーネントのクラスは Cape.Partial
クラスを継承し、必ず render()
メソッドを持たなければなりません。まだ骨組みを作っただけです。
次に、app/assets/javascripts
ディレクトリにある todo_list.es6
をテキストエディタで開いてください。現在、renderCreateForm()
メソッドが次のように定義されています。
renderCreateForm(m) {
m.formFor('new_task', m => {
m.onkeyup(e => this.refresh());
m.textField('title', { value: this.val('new_task.title') }).sp();
m.attr({ disabled: this.val('new_task.title').trim() === '' });
m.onclick(e =>
this.agent.createTask(this.val('new_task.title', '')));
m.btn(`Add task #${ this.agent.objects.length + 1 }`);
});
}
このメソッドの中身(m.formFor(m) {
から });
まで)をコピーして、さきほど新規作成した create_form.es6
の render()
メソッドの中身として貼り付けてください。すると、create_form.es6
のソースコードはこのようになりますね。
class CreateForm extends Cape.Partial {
render(m) {
m.formFor('new_task', m => {
m.onkeyup(e => this.refresh());
m.textField('title', { value: this.val('new_task.title') }).sp();
m.attr({ disabled: this.val('new_task.title').trim() === '' });
m.onclick(e =>
this.agent.createTask(this.val('new_task.title', '')));
m.btn(`Add task #${ this.agent.objects.length + 1 }`);
});
}
}
移設したコードの中では this.refresh()
とか this.val()
などのメソッドが呼び出されています。前回書いたことの繰り返しになりますが、これらのメソッドは親コンポーネントの同名のメソッドを呼び出します。
つまり、this.refresh()
は部分コンポーネントを再描画するのではなく親コンポーネントを再描画します。親コンポーネントを再描画すれば、それが内包している部分コンポーネントも再描画されるので、結局はこの部分コンポーネント(CreateForm
クラスのインスタンス)自体も再描画されることになります。
他方、this.agent
はこの部分コンポーネント自体の agent
属性を指します。この属性はまだ定義されていないので、このままではエラーを引き起こします。この点は覚えておきましょう。
ここまでが書き換え作業の第一段階です。
第二段階へ進みます。todo_list.es6
に次のようなコードがあります。
init() {
this.agent = new TaskCollectionAgent(this);
this.editingTask = null;
this.agent.refresh();
}
これを次のように書き換えてください(3 行目を挿入)。
init() {
this.agent = new TaskCollectionAgent(this);
this.createForm = new CreateForm(this);
this.editingTask = null;
this.agent.refresh();
}
new CreateForm(this)
で部分コンポーネントを作り、それを親コンポーネントの createForm
属性にセットしています。部分コンポーネントのコンストラクタの引数には親コンポーネント自体を渡すことになっています。
さらに todo_list.es6
を書き換えます。現在 render()
メソッドのコードは次の通り。
render(m) {
m.ul(m => {
this.agent.objects.forEach((task, index) => {
m.li(m => {
this.renderTask(m, task);
this.renderButtons(m, task, index);
});
});
});
if (this.editingTask) this.renderUpdateForm(m);
else this.renderCreateForm(m);
}
これの下から 2 行目を次のように書き換えてください。
else this.createForm.render(m);
属性 this.createForm
には部分コンポーネントがセットされており、その render()
メソッドを呼び出しています。部分コンポーネントの render()
メソッドは、第 1 引数にマークアップビルダー(m
)を取ります。
ソースコードの書き換え第三段階では、さきほど記憶にとどめておいた問題に対応します。
いま、CreateForm
クラスの render()
メソッドの中身はこうなっています。
m.formFor('new_task', m => {
m.onkeyup(e => this.refresh());
m.textField('title', { value: this.val('new_task.title') }).sp();
m.attr({ disabled: this.val('new_task.title').trim() === '' });
m.onclick(e =>
this.agent.createTask(this.val('new_task.title', '')));
m.btn(`Add task #${ this.agent.objects.length + 1 }`);
2 箇所で this.agent
という記述があり、ここでエラーが発生します。コピー元のソースコードでは、this.agent
はリソースエージェント TaskCollectionAgent
のインスタンスを指しています。この参照関係を維持するにはどうすればいいでしょうか。
部分コンポーネントの parent
属性には親コンポーネントのインスタンスがセットされています。とすれば、2 箇所の this.agent
を this.parent.agent
に書き換えてやればいいということになります。
しかし、前回紹介した技法を用いれば、もっとエレガントに書けます。create_form.es6
のソースコードを次のように書き換えてください。
class CreateForm extends Cape.Partial {
constructor(parent) {
super(parent);
this.agent = parent.agent;
}
render(m) {
(省略)
}
}
CreateForm
クラスのコンストラクタを上書きしています。先述の通り、部分コンポーネントのコンストラクタは第 1 引数として親コンポーネントを取りますので、親コンポーネントの agent
属性が参照するオブジェクトをこの部分コンポーネントの agent
属性にセットしていることになります。こうすれば 部分コンポーネントの render()
メソッドを書き換える必要がありません。
最後に、todo_list.es6
から使われなくなった renderCreateForm()
メソッドのコードを削除しましょう。
以上で、親コンポーネントである TodoList
クラスからコードの一部を抽出して部分コンポーネント CreateForm
クラスを作る作業は終了です。
結果として、todo_list.es6
のソースコードの行数は 95 から 85 に減少しました。
ブラウザをリロードして TODO アプリが従来通り動くことを確認してください。
なお、書き換え後のソースコードは
https://github.com/oiax/capejs-tut2-app-source/archive/lecture-02.zip
から取得できます。
また、今回の変更内容は
- 9914a00 Bump rails and sprockets-es6
- 6acfaa5 Bump capejs and bootstrap
- 85df966 Extract CreateForm partial component
で確認できます。
次回は、TodoList
クラスからタスク編集フォームの描画とタスクの更新に関わる部分を部分コンポーネントとして抜き出します。