背景を暗くするモーダルウィンドウで表示したい!(Control.Modalの使い方)その1

初めて見た時からすごく憧れていたのだ。最近は、いろんなページでよく見かける。写真をクリックすると、そのページの明度が下がって、拡大された写真が表示されたウィンドウが浮かび上がる、アレ(Lightbox)です。MacBookでF12を押すとDashboardが起動するが、その時ウィンドウが表示されるイメージだ。周囲の背景に惑わされず、必要な情報が自然と目に入って来る。すごくシンプルなんだけど、洗練されたデザインだ。
最近では、LightBoxと似たような効果を演出してくれるライブラリがたくさん公開されているらしく、CSVサーバーにもぜひ導入してみたい!(Lightbox.js のような写真のスライドショーするライブラリ沢山)きっと、ユーザー環境を設定するページで利用するのが良さそう。というより、絶対、モーダルウィンドウで表示したい!ということで、試行錯誤の導入の始まり...。

調査

今回必要なのは、画像だけでなく、webページとしてウィンドウ内に表示してくれるライブラリだ。調べてみると、こちらのJavaScript - youmosControl.Modalを発見。これを利用してみることにした。

利用環境

ダウンロード

  • control.modal.js
    • Control.Modalのページ右上より、Download 1.2.12 ボタンを右クリックしてダウンロードした。
  • prototype.js
    • Control.Modalが動作するためには、Prototypeライブラリも必要だ。
    • 自分の環境のRailsに含まれていたPrototypeは、バージョンが1.5.0_rc0と古かった。(このバージョンでは動かなかった。)
    • Prototypeのダウンロードページ上段より、Download the latest stable version―1.5.1 リンクを右クリックしてダウンロードした。
      • 現在の自分のRails環境では、version―1.5.1ではin_place_editorが動かなくなってしまうことが発覚。バージョンを落としてversion―1.5.0を利用するとで解消できた。(モーダルウィンドウもin_place_editorも全てがうまく稼働する。)

JavaScriptのインストール

ダウンロードしたファイルは、Railsプロジェクトなので、以下のディレクトリに移動した。

  • public/javascripts/ ディレクトリに、ダウンロードした2ファイルを移動してインストールする。
      • インストール方法:MacOSのFinderでファイルを移動、またはRadRailsのフォルダツリーに直接ドラッグド&ロップ、どちらでもOK。

スタイルシートの準備

  • public/stylesheets/ ディレクトリに、新規ファイルを作成した。(ファイル名:control.modal.css
  • Control.Modalのページのソースをwebブラウザで表示して、モーダルウィンドウ用のスタイルシート部分をコピーする。
  • 先程作成したpublic/stylesheets/control.modal.css にそのまま貼付ける。不要な空白を削除すると、以下のようなコードになると思う。
#modal_container {
  padding:5px;
  background-color:#fff;
  border:1px solid #666;
  overflow:auto;
  font-family:"Lucida Grande",Verdana;
  font-size:12px;
  color:#333;
  text-align:left;
}

#modal_container.test {
  background-color:#900;
  color:#fff;
  padding:30px;
  text-align:center
}

#modal_overlay {
  background-color:#000;
}

#modal_overlay.test {
  background-color:#fff;
}

レイアウトファイルの修正

app/views/layouts/csvs.rhtml
レイアウト
<div id="layout_update">
<html>
<head>
  <title>Csvs: <%= controller.action_name %></title>
  <%= stylesheet_link_tag 'body' %>
  <%= stylesheet_link_tag 'scaffold' %>
  <%= stylesheet_link_tag 'scaffold_table' %>
  <%= stylesheet_link_tag 'list_o_matic' %>
  <%= javascript_include_tag :defaults %>

  <%= javascript_include_tag 'prototype' %>       <%#<------追記1 %>
  <%= javascript_include_tag 'control.modal.js' %><%#<------追記2 %>
  <%= stylesheet_link_tag 'control.modal.css' %>  <%#<------追記3 %>
</head>
<body>
<%= periodically_call_remote :url=>{:action=>'check_flash_timer'}, 
                             :frequency=>10 %>
<%# render :partial=>"menu"だと、"softwares/menu2"の描画になってしまう。 %>
<%= render :partial=>"layouts/menu2" %>
<%= render :partial=>"layouts/flash" %>

<%= yield  %>

</body>
</html>
</div>
  • javascript_include_tagでは、ファイル名の拡張子は自動的に補完してくれるので不要なのだが、今回のようにファイル名に「.」(ドット)が含まれていると、どうもその補完がうまく機能してくれないようだ。拡張子まで含めて指定することで、ちゃんと読み込んでくれるようになった。('control.modal.css'も同様)
  • javascript_include_tag :defaultsがあるので、javascript_include_tag 'prototype'は不要だと思ったが、どうも自分の環境では省略するとうまく動かなかった。
      • prototypeのバージョンを1.5.0にすることで、javascript_include_tag :defaultsの宣言だけでOKになった。prototype1.5.1ではin_place_editorが利用できなくなる状態になっていた。そもそも、自分のRails環境に合わないバージョンをダウンロードしていたのが問題だったのだ。

JavaScriptの追記

メニューバーの「設定」リンクをクリックした時に、モーダールウィンドウを表示するので、_menu2.rhtmlに以下のコードを追記した。(listアクションで読み込まれるファイルなら、どこに置いても良いと思う。)

app/views/layouts/_menu2.rhtml
メニューの描画を担当
  • iframe: true以下は、モーダルウィンドウのオプション指定。
  • 以下の指定で、ページ遷移可能なモーダルウィンドウ(幅640×高さ480)を表示してくれる。
    • ちなみに、iframe: falseにしてしまうと、モーダルウィンドウの中でページ遷移できなかった。(モーダルウィンドウの中でリンクをクリックすると、モーダル状態が解除され、メインページに戻って、その内容が表示された。)
    • iframe: trueの場合、ウィンドウサイズを指定しないと、モーダルウィンドウが変なサイズで表示されてしまった...。
  • Control.ModalのAdvanced Usageにオプション指定の一覧が示されている。
...(途中省略)...
<%= javascript_tag <<END_javascript_tag
  // $$('a.modal')は、<a class="modal">である要素オブジェクト(element)を全て取得する。
  $$('a.modal').each(function(link){
    new Control.Modal(link,{
      iframe: true,
      width: 640,
      height: 480
    });
  });
END_javascript_tag
%>

モーダルウィンドウで表示

以上で準備完了。これまでの修正を追記しても、今までと変わりなく、CSVサーバーは動いている。そして、もしモーダルウィンドウで表示したいページがあった場合は、以下のように追記するだけでOKだ。今回は試しに、ユーザー環境の設定ページをモーダルウィンドウで表示してみる。

app/views/layouts/_menu2.rhtml
メニューの描画を担当
  • link_if_authorized_current '設定'タグ内に、:class=>"modal"を追加しただけ。
  • 他にもモーダルウィンドウで表示したいページがあったら、同じように:class=>"modal"を追加するだけでOK。
  • 準備に手間はかかったが、一度設定してしまえば手軽に利用できる!
...(途中省略)...
<div id="navcontainer">
  <ul id="navlist">
  <%= link_if_authorized_current 'CSVリスト', 
        {:controller=>'csvs', :action=>'list'}, :wrap_in=>"li" %>
  <%= link_if_authorized_current '設定', 
        {:controller=>'defaults', :action=>'edit_preference'}, :wrap_in=>"li", :class=>"modal" %>
...(途中省略)...

これでモーダルウィンドウが表示されるようになる。ただし現状では、本来は不要なメニューも描画されてしまっている...。

モーダルウィンドウの閉じ方

  • モーダルウィンドウは、ウィンドウ外側のオーバーレイ部分をクリックすれば、モーダル状態が解除され、元のメインページに戻る。(特にオプションを変更していない場合)
  • もしオーバーレイ部分をクリックしても閉じたくない場合は、overlayCloseOnClick: false と指定すればOK。
  • 上記の設定にした場合は、明示的に「キャンセル」や「閉じる」などの、リンクなりボタン等を表示しておかないと、モーダルウィンドウの閉じ方で悩んでしまうユーザーが出るかもしれない...。
    • キーボードの、エスケープ(esc)ボタンでもモーダル状態を解除できるが、分かり難いと思う。
    • 例えオーバーレイ部分をクリックして閉じられる環境でも、「キャンセル」や「閉じる」を表示した方が親切だと思う。
  • モーダルウィンドウの中で、モーダルウィンドウ自身を閉じるリンクを作成するには、以下のように追記すればOK。
    • parentを追記するのがミソ。parent無しの Control.Modal.close(); は、メインページからモーダルウィンドウを全て閉じたい時に使うようだ。
<%= link_to_function 'キャンセル', "parent.Control.Modal.close();" %>

リンク先とレイアウトファイルの変更

iframe: trueのモーダルウィンドウは、開いている限り、そのページの中でひたすらページ遷移していく。*1なので、現在の状態では「キャンセル」や「更新」をクリックすると、モーダルウィンドウを開いたまま、その中でメインページのCSVリストを表示してしまう結果になる...。それを回避するため、以下のようにやってみた。

  • リンク先は常にユーザー環境設定ページに戻るように変更した。
  • 但し、キャンセルリンクについては、ウィンドウを閉じるJavaScriptを設定した。
キャンセルリンク
app/views/defaults/edit_preference.rhtml
ビュー(ユーザー環境設定の描画を担当)
  • キャンセルリンクをモーダルウィンドウを閉じるリンクに変更した。
<h2>ユーザー環境の設定</h2>

<div>
<%= link_to 'アプリケーションの初期設定へ戻す', :action => 'set_app_default' %>
</div>
<br />

<%= start_form_tag :action => 'update_preference', :id => @default %>
  <%= render :partial => 'form_preference' %>
  <br />
  
  <div>
  <%#= link_to 'キャンセル', :controller => 'csvs', :action => 'list' %>
  <%= link_to_function 'キャンセル', "parent.Control.Modal.close();" %> |<%#<------変更 %>
  <%= submit_tag '更新', :id=>'submit' %>
  </div>
<%= end_form_tag %>
更新ボタンとレイアウトファイル
app/controllers/defaults_controller.rb
コントローラー
  • 描画するページを常にedit_preference.rhtmlに変更した。
  • モーダルウィンドウの中ではメニューを表示したくないので、メニューの描画を除外したレイアウトファイル(modal.rhtml)を利用するように変更した。
class DefaultsController < ApplicationController
  layout 'application', :except => [:edit_preference, :update_preference, :set_app_default]
...(途中省略)...
  def edit_preference
    @column_titles = Csv.show_column_names
    @default = current_user.default
    @default_hash = default_user(true)
    render :layout=>'modal'
  end
  
  def set_app_default
    @column_titles = Csv.show_column_names
    @default = current_user.default
    @default_hash = default_app
    render :action => 'edit_preference', :layout=>'modal'
  end
  
  def update_preference
    current_user.create_default(:yaml => {}.to_yaml) if current_user.default.nil?
    @column_titles = Csv.show_column_names
    @default = current_user.default
    @default_hash = default_user.merge(params[:default])
    if current_user.default.update_attributes(:yaml => @default_hash.to_yaml)
      flash[:notice] = 'ユーザー環境を更新しました。'
      #redirect_to :controller => 'csvs', :action => 'list'
      render :action => 'edit_preference', :layout=>'modal'
    else
      render :action => 'edit_preference', :layout=>'modal'
      # 強制リロードを有効にして、キャンセルした時にキャッシュに残らないようにする。
      @default_hash = default_user(true)
    end
  end
...(途中省略)...
app/views/layouts/modal.rhtml
レイアウト(メニューバーを除外して描画する)
  • 基本的にapplication.rhtmlのレイアウトファイルと同じ。削除1、2でメニューに関する部分を削除しただけ。
<html>
<head>
  <title><%= controller.controller_name %>: <%= controller.action_name %></title>
  <%# スタイルシートは、後の設定が優先される...とは限らない。 %>
  <%= stylesheet_link_tag 'body' %>
  <%#= stylesheet_link_tag 'list_o_matic' %><%#<------削除1:メニューのスタイルシートの読み込みを停止した。 %>
  <%= javascript_include_tag :defaults %>
  
  <%= javascript_include_tag 'prototype' %>
  <%= javascript_include_tag 'control.modal.js' %>
  <%= stylesheet_link_tag 'control.modal.css' %>
  
  <%= engine_stylesheet 'login_engine' %>
  <%= engine_stylesheet 'user_engine' %>
  <%= engine_javascript "user_engine" %>
</head>

<body>
<%# render :partial=>"menu"だと、"softwares/menu"の描画になってしまう。 %>
<%#= render :partial=>"layouts/menu2" %><%#<------削除2:メニューの描画をを停止した。 %>
<%= render :partial=>"layouts/flash" %>

<%= yield  %>
</body>
</html>


以上の設定を全て行うと、目指していたモーダルウィンドウがちゃんと表示されるようになった!

*1:モーダルウィンドウ中のリンクをクリックすると、そのリンク先をモーダルウィンドウの中に表示するということ。メインページに戻るためには、モーダルウィンドウを閉じる必要がある。