第5回: Ajax (2)

2012/03/15

前回に引き続き、テーマはAjaxです。

正方形をドラッグして位置を変更した場合の処理を実装します。

blocks#updateアクションの実装

Ajaxコールを受けるblocks#updateアクションを実装します。

config/routes.rb を修正。

Toraja::Application.routes.draw do
  root to: "top#index"
  resources :blocks, only: [ :create, :update ]
end

app/controllers/blocks_controller.rb を修正。

class BlocksController < ApplicationController
  def create
    block = Block.create!(params[:block])
    render text: block.id
  end

  def update
    block = Block.find(params[:id])
    block.update_attributes!(params[:block])
    render text: "OK"
  end
end

top#indexアクションのビューを修正

app/views/top/index.html.erb を修正。

 <% @blocks.each do |block| %>
 <%= content_tag(:div, nil, class: "block",
     data: { :"block-id" => block.id },
     style: "left: #{block.x}px; top: #{block.y}px") %>
 <% end %>

3行目に data: { :"block-id" => block.id }, を挿入しています。

この結果、div要素に data-block-id という属性が追加され、CoffeeScript 側でBlockオブジェクトの主キー(id)の値を参照できるようになります。

Ruby 1.9で導入された { key: value } というハッシュの記述法は、キーがシンボルで、かつキーの名前がアルファベットとアンダースコア(_)のみで構成されている場合にしか使えません。この例のように、ハイフン(-)を含むキーの場合には、伝統的な => を用いて記述してください。

正方形の位置を更新

現在、app/assets/javascripts/top.js.coffee はこうなっています。

$ ->
  $("div#canvas").dblclick (e) ->
    [x, y] = positionOfNewBlock(e)
    $.post '/blocks', block: { x: x, y: y }, (block_id) ->
      block = $("<div class='block' style='left: #{x}px; top: #{y}px;' />").
        draggable(containment: "parent").css(position: "absolute")
      $(e.target).append(block)

  $("div.block").draggable(containment: "parent").css(position: "absolute")

positionOfNewBlock = (e) ->
  canvas = $(e.target)
  x = e.pageX - canvas.position().left
  y = e.pageY - canvas.position().top
  [x, y]

まず、5-6行目を次のように変更してください。data("blockId", block_id). という1行を挿入しています。

      block = $("<div class='block' style='left: #{x}px; top: #{y}px;' />").
        data("blockId", block_id).
        draggable(containment: "parent").css(position: "absolute")

data("blockId", block_id) で、生成された要素(正方形)の data-block-id 属性に値をセットしています。これがBlockオブジェクトの主キー(id)の値です。正方形の位置を変更するためのAjaxリクエストで、この値が用いられます。

属性の名前はハイフンで区切られていますが、data メソッドに渡す名前はキャメルケースに変換する点に注意してください。

次に、9行目の $("div.block").draggable ... の次の行に以下のコードを挿入してください。

  $(document).on "dragstop", "div.block", (e) ->
    block = $(e.target)
    blockId = block.data("blockId")
    x = parseInt(block.css("left"))
    y = parseInt(block.css("top"))
    $.ajax "/blocks/#{blockId}", type: "PUT", data: { block: { x: x, y: y } }

では、少しずつ見ていきましょう。

  $(document).on "dragstop", "div.block", (e) ->

CSSセレクタ div.block に該当する要素で dragstop というイベントが発生したら、-> 以下のコードを実行しなさい、という意味になります。

dragstop というイベントは標準的なイベントではなく、jQuery UIのDraggableプラグインによって提供されているものです。

on は、jQuery 1.7で導入された比較的新しいメソッドです。要素に対してイベントハンドラを割り当ててくれます。

jQuery 1.6までは live というメソッドでイベントハンドラの割り当てを行っていましたが、「廃止予定(deprecated)」になりました。

なお、いま自分が使っているjQueryのバージョンを調べるには、top.js.coffee の先頭に alert $(document).jquery を挿入して、ブラウザをリロードしてください。

    block = $(e.target)
    blockId = block.data("blockId")

dragstop イベントが発生した要素(正方形)の data-block-id 属性の値を変数 blockId にセットしています。

    x = parseInt(block.css("left"))
    y = parseInt(block.css("top"))

正方形の左上隅の位置を変数 x, y にセットしています。css メソッドが返す値には px という単位が付いているので、parseInt でメソッドで整数値に変換しています。

    $.ajax "/blocks/#{blockId}", type: "PUT", data: { block: { x: x, y: y } }

正方形のidが123であるとすれば、/blocks/123 というURLにPUTメソッドでアクセスをしています。POSTメソッドでアクセスする場合とは、引数の与え方が異なるので注意してください。

修正が完了したら、動作確認をしましょう。ブラウザに戻り、ページを再読込してから、正方形をドラッグして位置を動かしてください。もう一度ページを再読込しても正方形の位置が変化しなければOKです。正方形がドラッグ前の位置に戻ってしまう場合は、Ajaxによるアクセスがうまくいっていません。ソースコードをじっくりと見直してください。

更新情報

  • 「まず、5-6行目を次のように変更してください。」から「次に、」までを追加しました。(2012/3/16)
  • ソースコードを修正しました。 (誤) $("div.block").on "dragstop", (e) -> (正) $(document).on "dragstop", "div.block", (e) -> (2012/3/17)
  • 節「top#indexアクションのビューを修正」を追加しました。今回はミスが多くて、読者の皆様に大変ご迷惑をおかけいたしました。お詫びいたします。(2012/3/18)