確実にメッセージをフェードアウトする。

MacBookの温度は快適になったが、periodically_call_remoteの頃から、見て見ぬ振りをしていた現象がある。それは、時々モーダルウィンドウのメッセージが消えずに、いつまでも表示され続ける、という現象だ。そろそろちゃんと対処したい。昨日から、どのような条件で発生するのか詳しく観察したところ、以下のことが確認できた。

  1. メッセージが表示されている最中に、さらに更新ボタンを押してしまうと、新たなメッセージが追加表示され、それ以前のメッセージがいつまでも表示され続ける。
  2. ページ全体を読み込んでから5秒以内だと、メッセージが1つしかない場合でも、いつまでも表示され続ける。

現在のメッセージを描画するコードは以下のようになっている。

app/views/layouts/_flash.rhtml
ビュー
<div id="flash">
<% for name in [:notice, :warning, :message] %>
<% if flash[name] %>
<br />
<div class="flash">
  <%= "<p style=\"color: green\">#{flash[name]}</p>" %>
  <% flash[name] = nil %>
</div>
<% end %>
<% end %>
<%= javascript_tag remote_function(:url=>{:action=>:check_flash_timer}) %>
</div>

眺めていたら、上記現象は<div id="flash">タグがネストして*1、1ページに同じid属性が複数存在するために発生していると気付いた。そこで、メッセージごとにオリジナルなid属性を設定することを考えた。
通し番号を付加しようと思ったが、webアプリショーションの場合、値を保持し続けることに手間がかかる。ここでは、重複しない値が欲しいだけなので、現在時刻を数値に変換して利用することにした。以下のようにやってみた。

app/views/layouts/_flash.rhtml
ビュー
  • 最初、flash_time = Time.nowとしていた。しかし、これだとパラメーターとして送信した時に、スペースが混じっているのでそれがエスケープされてしまい、ビューとコントローラーで異なるid属性を扱うことになってしまう。そのためto_iで整数に変換した。
<% flash_time = Time.now.to_i %><%#<------現在時刻を数値に変換 %>
<div id=<%= "flash_#{flash_time}" %>><%#<------式展開例: flash_1180853704 %>
  <% for name in [:notice, :warning, :message] %>
  <% if flash[name] %>
  <br />
  <div class="flash">
    <%= "<p style=\"color: green\">#{flash[name]}<small><small>[#{Time.now.strftime('%Y/%m/%d %H:%M:%S')}]</small></small></p>" %>
    <% flash[name] = nil %>
  </div>
  <% end %>
  <% end %>
  <%#v------以下パラメータを追加:flash_time=>flash_time %>
  <%= javascript_tag remote_function(:url=>{:action=>:check_flash_timer, :flash_time=>flash_time}) %>
</div>
app/controllers/application.rb
コントローラー
  • ビューからparams[:flash_time]でid属性を受け取って、その部分に対してビジュアルエフェクトをかけている。params[:flash_time]は、メッセージを表示する直前の時刻を数値に変換した値が保存されている*2
  def check_flash_timer
    if request.xhr?
      render :update do |p|
        p.delay(5) do
          p.visual_effect(:DropOut, "flash_#{params[:flash_time]}", :duration => 2)
        end
      end
    else
      redirect_to :controller=>'csvs', :action=>'list'
    end
  end

さらに修正

これでメーッセージは確実にフェードアウトしてくれる、と思っていたら、まだ問題があった。to_iでは1秒間に2回以上更新ボタンを押さない限り問題ないのだが、試しに更新ボタンをダブルクリックしてみると、なんと1秒間に2回以上操作できてしまうではないか...。でも、この修正は簡単で、to_iをto_f*3にするだけでOK。これでメッセージがいつまでも表示され続けることがなくなった!

*1:<div id="flash"><div id="flash">メッセージ</div></div>のような状態を表現している。

*2:1970年1月1日午前0時からの経過時間を秒数に変換した値になるそうだ。

*3:to_fに変換することで、秒未満の経過時間も小数点以下5桁までの数値で表現してくれるようだ。(to_~メソッドっててこの手軽さが好きだ!)これならどんなに細かくダブルクリックされても大丈夫なはず。(id属性は<div id=flash_1180909100.29249>のように設定される。)