Railsアプリにtimelineを組みこむ

レビュアブルマインドSIMILE | Timelineを入れてみたので、そのメモ。

ダウンロードと組みこみ

$ svn checkout http://simile.mit.edu/repository/timeline/

timeline/tags/1.2/src/webapp/api 以下を public/javascript/timelineにコピー

layoutの修正

api/timeline-apiを含める。

    %= javascript_include_tag 'timeline/api/timeline-api' if @need_timeline

bodyタグに、onloadとonresizeを入れる。

  % onload = onresize = nil
  % if @need_timeline
  %   onload = "onLoad();"
  %   onresize = "onResize();"
  % end

  <<body :| Attr[:onload=>:onload, :onresize=>:onresize ] <

コントローラの修正

ページ全体を表示する timeline というメソッドと、スケジュール項目をtimeline-apiの指定する形式のXMLで返す schedules という二つメソッドを追加。

class SchedulesController < ApplicationController
  before_filter :authorize

  around_filter :with_users_scope

  def timeline
    @need_timeline = true
  end

  def schedules
    @schedules = @user.schedules
    response.content_type = Mime::XML
    render :partial=>'schedules'
  end
end

ビューの修正

ビューは、タイムラインを表示するdivと二つのjavascript関数定義です。

<<div<
  <<div<
    This is an experimental feature.

  <div id="my-timeline" style="height: 400px; border: 1px solid #aaa"></div>

  <<%<
    <script type="text/javascript">
      (onLoadとonResizeとイベントハンドラを定義、下記参照)

javascript部分は、ほとんど本家のチュートリアルのまる写しですが、Timeline.loadXMLという所のパスだけ、今回定義したアクションに変更しました。

      // (schedule/timeline.a2html)
      var tl;
      function onLoad() {
        var eventSource = new Timeline.DefaultEventSource();
        var bandInfos = [
          Timeline.createBandInfo({
              eventSource:    eventSource,
              width:          "70%",
              intervalUnit:   Timeline.DateTime.DAY,
              intervalPixels: 100
          }),
          Timeline.createBandInfo({
              eventSource:    eventSource,
              showEventText:  false,
              trackHeight:    0.5,
              trackGap:       0.2,
              width:          "30%",
              intervalUnit:   Timeline.DateTime.MONTH,
              intervalPixels: 200
          })
        ];
        bandInfos[1].syncWith = 0;
        bandInfos[1].highlight = true;

        tl = Timeline.create(document.getElementById("my-timeline"), bandInfos);
        Timeline.loadXML("schedules", function(xml, url) { eventSource.loadXML(xml, url); });
      }

      var resizeTimerID = null;
      function onResize() {
          if (resizeTimerID == null) {
              resizeTimerID = window.setTimeout(function() {
                  resizeTimerID = null;
                  tl.layout();
              }, 500);
          }
      }
// (schedule/_schedules.a2html)
<<data<
    <<event :schedules | ModuleExtendFilter[TaskHelper] | Attr[:start=>:sch_date, :title=>:text, :taskId=>:id] <
      <<:text>>

このビューがこんな感じのXMLを生成します。

  <data>
    <event title="ロードマップ作成" taskId="428" start="Mon Jan 14 00:00:00 2008">ロードマップ作成</event>
    <event title="日本 Ruby 会議 2008 - FrontPage" taskId="217" start="Sun Feb 17 00:00:00 2008">日本 Ruby 会議 2008 - FrontPage</event>
    ...
  </data>

taskIdという属性値は、この後のカスタマイズの為に設定しています。

クリックした時の表示内容をカスタマイズ

以上で、timeline自体は表示されますが、そのままだとtimeline内のイベントをクリックした時に、アプリケーション側に制御を渡すことなく、既定のポップアップウィンドウでevent要素のデータが表示されてしまいます。

そこで、イベントハンドラを入れ替えて、アプリケーション側で指定した処理をさせています。

  Timeline.DurationEventPainter.prototype._onClickInstantEvent = function(icon, domEvt, evt) {
    var task = evt._node.getAttribute("taskId") ;
    new Ajax.Updater('task_work', '../main/show_task_window/' + task.toString(),
                  {
                         asynchronous:true,
                         evalScripts:true,
                         method:'post',
                         onSuccess: function(o) {
                           TaskWindow.show();
                         }
                   }
                   );
  }

evtというのは、ソースとして指定したXMLのevent要素です。これが、イベントのコールバックに渡されるので、アプリケーション側の処理に必要な情報(この場合はtaskID)を含めておいて、それを取り出して処理しています。