タスクの新規追加
2015/07/03
今回は、前回作ったタスクの新規追加フォームからタスクをデータベースに追加する機能を実装します。
まず、コンポーネント側から書き換えます。現在、renderCreateForm()
のソースコードは次のようになっています。
renderCreateForm(m) {
m.formFor('new_task', m => {
m.textField('title').sp();
m.btn(`Add task #${ this.ds.tasks.length + 1 }`);
});
}
これを次のように変更します(2行挿入)。
renderCreateForm(m) {
m.formFor('new_task', m => {
m.textField('title').sp();
m.onclick(e =>
this.ds.createTask(this.val('new_task.title')));
m.btn(`Add task #${ this.ds.tasks.length + 1 }`);
});
}
この結果、タスクの新規追加フォームのボタンをクリックしたときに this.ds.createTask(this.val('new_task.title'))
というコードが実行されることになります。
コンポーネントの val()
メソッドはフォームフィールドの値を返します。引数には x.y
という形式の文字列を渡します。x
の部分がフォーム名で y
の部分がフィールド名です。ここでは new_task
という名前のフォームにある title
フィールドの値を取得して、データストアの createTask()
メソッドに渡しています。
次に、データストア側に createTask()
メソッドを追加します。task_store.es6
を次のように書き換えてください。
class TaskStore extends Cape.DataStore {
constructor() { ... }
refresh() { ... }
createTask(title) {
$.ajax({
type: 'POST',
url: '/api/tasks',
data: { task: { title: title } }
}).done(data => {
if (data === 'OK') this.refresh();
});
}
toggleTask(task) { ... }
}
追加された createTask()
メソッドは、引数としてタスクのタイトルを取ります。それを POST メソッドで /api/tasks
に対して Ajax で送信し、返ってきたデータが OK
であれば this.refresh()
を実行します。this.refresh()
は Rails アプリケーションの API にアクセスしてタスクのリストを取得し、コンポーネントを再描画します。
続いて、API を作ります。テキストエディタで app/controllers/api/tasks_controller.rb
を開いて、次のように書き換えます(create
メソッドを追加)。
class Api::TasksController < ApplicationController
def index
...
end
def create
if Task.create(task_params)
render text: 'OK'
else
render text: 'NG'
end
end
def update
...
end
private
def task_params
params.require(:task).permit(:title, :done)
end
end
Task.create
メソッドは tasks
テーブルにレコードを追加します。
最後に config/routes.rb
を書き換えて完成です(下から3行目に :create,
を追加)。
Rails.application.routes.draw do
root 'top#index'
namespace :api do
resources :tasks, only: [ :index, :create, :update ]
end
end
ブラウザで動作確認をしましょう。新しいタスクのタイトルを書き入れて…
ボタンをクリックすると…
タスクが追加されました。
でも、ちょっと変ですね。タスク追加が済んだらフォームに記入したタイトルは消えてほしいところです。そのためには、コンポーネントの renderCreateForm()
メソッドを次のように書き換えます。
renderCreateForm(m) {
m.formFor('new_task', m => {
m.textField('title').sp();
m.onclick(e =>
this.ds.createTask(this.val('new_task.title', '')));
m.btn(`Add task #${ this.ds.tasks.length + 1 }`);
});
}
変更点は下から4行目です。this.val('new_task.title')
を this.val('new_task.title', '')
に変えました。
コンポーネントの val()
メソッドは引数の個数が 1 個の場合、単にフィールドの値を返すだけですが、第 2 引数が与えられた場合、フィールドの値を第 2 引数の値にセットしてから、フィールドの元の値を返します。ここでは、タスクの新規追加フォームの title
フィールドを空にしてから、そこに書かれていた文字列をデータストアの createTask
メソッドに渡しています。
ブラウザで動作確認をしてください。タスク追加の直後、フォームの title
フィールドの中が空になっていれば OK です。
もうひとつ UI の改善をしてみましょう。現状では、タイトルが空のままでもボタンをクリックできて、中身のないタスクが作られてしまいます。タイトルが空である間は、ボタンを無効化することにしましょう。
まず、効果が見えやすいようにスタイルシートの準備をします。app/assets/stylesheets/todo_list.scss
を次のように書き換えてください(3行追加)。
#todo-list {
label.completed span {
color: #888;
text-decoration: line-through;
}
button[disabled] {
color: #888;
}
}
そして、コンポーネントの renderCreateForm()
メソッドを次のように変更します。
renderCreateForm(m) {
m.formFor('new_task', m => {
m.onkeyup(e => this.refresh());
m.textField('title').sp();
m.attr({ disabled: this.val('new_task.title').trim() === '' });
m.onclick(e =>
this.ds.createTask(this.val('new_task.title', '')));
m.btn(`Add task #${ this.ds.tasks.length + 1 }`);
});
}
2行追加しています。まず m.onkeyup(e => this.refresh());
を挿入して、タイトルフィールドの中身が書き換わるたびにコンポーネントの再描画を行っています。イベント keyup
は、キーボードのキーが押されて上がった際に発生します。また、マウスを使って文字列が貼り付けられた時にも発生します。
もうひとつ挿入されたのは次のコードです。
m.attr({ disabled: this.val('new_task.title').trim() === '' });
ちょっとややこしく見えますが、単純なことです。タイトルフィールドの中身を trim()
で両端の空白を削ってから、それが空文字なら disabled
属性をボタンに設定しています。
ユーザーがタイトルフィールドの中身を書き換えるたびに、コンポーネント全体が再描画されます。中身が空ならボタンが無効化され、空でなければ有効化されます。
ブラウザをリロードした直後の画面では、次のようにボタンが無効化されています。
しかし、タイトルフィールドに「あ」という 1 文字を書き入れると、
ボタンが有効化されます。そして、「あ」を削除すれば再び無効化されます。
この種の効果を jQuery で実現するのは意外に面倒です。仮想 DOM が活きる場面です。
次回は、タスクのタイトルを編集する機能を作ります。