div/js
Divに追加したJavaScriptサポートについて。やっぱ1.3.2でAPI変更しました。ごめんなさい。 お急ぎの方はdivip-1.0.1、かsample/jsを読んでください。
Divによるサポート
Ajaxの記事に触発されてDHTMLをやってみようかと思ったんですが、JavaScriptやXMLを 駆使するなにかはめんどくさいのでさっさとあきらめてしまいました。 ページ片をつくるのはサーバに、インタラクションやページ片の表示はクライアントにしてもらう ことにしたのです。
Divが提供するのはページの遷移なしにDiv::Divの内容を更新する仕組みです。 Div::Div単位にHTML片を取り出すのを簡単にすることと簡単なJavaScriptの準備を手伝います。
Div::DivはGUIでいうとひとまとまりのコントロール、Widgetみたいな単位です。 ページの遷移なしでWidgetを更新できます。ありがちな定期的な更新もサポートしています。
require 'div/js'
require 'div/js'
とするとDivとTofuSessionにJSサポートが追加されます。
Div#update_js
-
JavaScriptサポートのためのメソッド定義を返します。 div_x_eval()とdiv_x_update()の二つのJavaScriptのメソッドを定義します。
BaseDivのERBなどで次のように使用します。
<script language="javascript"> <%= update_js %> </script>
a_and_update(method_name, add_param, context, taget=nil)
-
Div#a()同様に「自分にmethod_name, add_paramで指定するメソッドを呼び出し、 targetで指定するDiv::Divを更新する」ためのJavaScriptを返します。 targetを省略すると、このDiv::Div自体が更新対象となります。
on_update_script(ary_or_script)
-
このDiv::DivがJavaScriptによって更新されたときに実行したいJavaScriptを 指示するメソッドです。ERBの中で使います。
<%= on_update_script("document.title = 'updated'") %>
update_me(context)
-
自分自身(このDiv::Div)をJavaScriptによって更新するJavaScriptを返します。 a_and_update()を利用したコンビニエンスメソッドです。次のスクリプトと等価です。
a_and_update('else', {}, context)
update_after(msec, context)
-
自分がJavaScriptによって更新されたとき、再び自分自身をmsec後に更新させることを 指示します。on_update_script()を利用したコンビニエンスメソッドです。
ERBで次のように使用します。
<%= update_after(msec, context) %>
sample/js
div/jsを使用した簡単な掲示板のサンプルアプリケーションです。 周期的に掲示板の内容を更新します。
board.rb
アプリケーション本体です。時系列の掲示板で時間と文字列だけ管理してます。 今回もRDBMSとか要らないです。(なかなかRDBMSが必要となるサンプルが思いつけない)
require 'thread' class Board class Message def initialize(who, str) @who = who @when = Time.now @message = str end attr_reader :who, :when, :message end include Enumerable def initialize @list = [] @mutex = Mutex.new end def add(who, str) msg = Message.new(who, str) @mutex.synchronize do @list.unshift(msg) end msg end def each(&block) @list.each(&block) end end
board_div.rb
GUI部分のコードです。いつものDiv::Divです。ちょっと違うのがSessionのdo_GETです。 do_inner_html()というメソッドを呼ぶと、HTML片を要求するリクエストの場合には 指定のDiv::DivのHTML片を生成し、レスポンスを作成します。 処理した時はtrue、HTML片を要求するリクエストでなかったらfalseを返します。falseのときは いつもの処理をします。
class BoardTofuSession < Div::TofuSession def do_GET(context) .... update_div(context) return if do_inner_html(context) body = @base.to_html(context) context.res_header('content-type', 'text/html; charset=euc-jp') context.res_body(body) end end
board_base.erb
台紙となるERBスクリプトです。
<html> <head> <script language="javascript"> <%= update_js %> </script> <title>board</title> </head> <body <% if @session.nick %>onload='setTimeout(<%= @log_div.update_me(context).dump%>, <%= @session.interval %>)'<% end %> > <h1>board</h1> <% if @session.nick %><%= @add_div.to_div(context) %><% else %><%= @nick_div.to_div(context) %><% end %> <%= @log_div.to_div(context) %> </body> </html>
ここで初めてJavaScriptが出てきます。 update_js()はJSサポートに必要なJavaScriptを返すメソッドです。 HEADの辺りに書いてください。
<script language="javascript"> <%= update_inner_js %> </script>
update_me()はJavaScriptのinnerHTMLを利用してDiv::Div単位に(ページ遷移なしに) Div::Divを更新するスクリプトを出力します。 例ではsetTimeoutの引数とするのでメソッドの結果にさらにdumpしています。
<body <% if @session.nick %> onload='setTimeout(<%= @log_div.update_me(context).dump%>, <%= @session.interval %>)' <% end %> >
セッションにnickが設定されていれば(つまりニックネームの設定が済んでいれば)、 body要素のonload属性に setTimeout() を指定します。 setTimeout()の引数には、@log_divを更新するスクリプト(update_me)と ディレイ時間を与えます。
ちょっとまとめます。board_base.erbでは次のことをしています。
- JSサポートに必要なJavaScriptの記述
- bodyのonloadに、@log_divを後で更新するスクリプトを設定
board_log.erb
board_logは掲示板の表示を担当するDiv::Divです。 最近の10行を表示します。
このサンプルアプリケーションでAjax風に非同期に更新するのはこのDiv::Divです。 最初のupdate_after()は、JSでDiv::Divを更新された際に指定したディレイの後に 再び更新することを指示するメソッドです。 現在の実装ではhiddenなinput要素に、更新時にevalしたいスクリプトを忍ばせ、 update_js()の仕掛けたスクリプトがそれを探して実行するようになっています。
update_after()は自身を指定時間後に更新するスクリプトを出力しますが、 on_update_script()を使用すると任意のスクリプトを出力することが可能です *1。
<%= update_after(session.interval, context) %> <table> <% board.each_with_index do |msg, idx| %> <tr> <td><%= msg.when.strftime('%H:%M:%S') %></td> <td><%=h msg.who %></td> <th><%=h msg.message%></th> </tr> <% break if idx >= @lines end %> </table>
*1前述したように、update_afterはon_update_scriptで作られている