26. システム設定画面における view の記述

旧システムでもシステム設定画面はさまざまな権限用のメニューが沢山用意されていた。このため、非常に長いページになり、スクロールが必要であった。今回は、nav-tabs を使ってタブ表示を行う。これが一つ目の改定。

また、これまでメニューの部分は以下の様に記述していた。

      %tr
        %th= link_to_if_lh @is_kyoumu || @is_chousei, '授業設定', teacher_jugyous_path
        %td 授業を設定します.
        %td 教務室・調整係

リンクの活性・非活性については、link_to_if_lh というメソッドを用意し、権限の変数を確認して設定していた。しかし、実際の権限表示の文字列は手書きであり、これがミスマッチをしていたことが多かった。さらに、コントローラでは before_action で判断をしており、リンク先に飛んでみたものの権限がないと表示されることも多かった。これは、一つの権限の変更により、リンクの活性状態変更、説明の描画、コントローラのフィルタ変更と 3 箇所を修正する必要があったためだ。

以前、権限設定の部分で説明したように、この部分をコントローラの Privileges というハッシュで管理することにした。そこでメニュー描画もこのハッシュを用いて自動化することにする。これが二つ目の改定。

以下ではこの二つの改定を実装する。

navi_tab によるタブ表示

まず、teacher/system_menus#index を作成する(作成方法は省略)。その後、app/controllers/teacher/system_menus_controller.rb でタブの設定を行う。タブ文字列の配列を用意し、権限によって、どのタブをデフォルトで active にするかを決めておく。ユーザインターフェース向上のため、その人が一番使いそうなタブがデフォルトで開くようにする。

  def index
    @links = %w( kyoumushitsu_menu gakuseishitsu_menu shinro_menu kaizen_menu kaikei_menu kyougaku_menu other_menu)
    @active = @links[is_kyoumu? ? 0 : is_gaku? ? 1 : is_shinro? ? 2 :
      is_kaizen? ? 3 : is_kaikei? ? 4 : is_kyougaku? ? 5 : 6]
  end

app/views/teacher/system_menus/index.html.haml は以下のようになる。@links で設定したタブの内容を nav-tabs で描画する。各タブに対応するコンテンツは .tab-content 以下に .tab-pane で id を付けて展開する。こうするだけで、Bootstrap の CSS が描画のオンオフを自動でやってくれる。タブの切替はすべて Javascript で行われるため、Rails 側で特殊なコーディングは必要ない。なお、まだコントローラなどは作成していないので、= を外して link_to などの文字列だけを表示している(実装が終わり次第修正していく)。

- content_for :title do
  = @title = t '.title'
- content_for :navi do
  = navi [ nil, teacher_system_menus_path ]

%ul.nav.nav-tabs
  -@links.each do |key|
    %li{ class_str_or_nil(key == @active, :active)}
      = link_to lh(t ".#{key}"), "\##{key}", 'data-toggle' => :tab
.tab-content
  - key = @links.shift
  .tab-pane{ class_str_or_nil(key == @active, :active), id: key }
    %table.table.table-striped.table-hover.table-bordered
      %tr
        - %w( リンク 内容 権限 ).each do |str|
          %th str 
      %tr
        %td link_to_if_lh @is_kyoumu, teacher_curriculums_path
        %td カリキュラムを設定します
        %td 教務室
 (中略)
  - key = @links.shift
  .tab-pane{ class_str_or_nil(key == @active, :active), id: key }
    %table.table.table-striped.table-hover.table-bordered
 (後略)

権限に応じたメニュー項目の表示切り替え

次に、teacher_curriculums_path の雛形だけをつくって、メニューを一つ変更してみた。ただし、ヘルパからユーザやセッションはよべないので、あらかじめ以下の様に取得しておく。

- ua = current_user_or_admin
- es = each_session

今回、menu_title と write_menu というヘルパーメソッドを作ることで、以下の様に記述できるようにする。基本的にはリンクと文字列だけを記載すればよい。

      = menu_title
      = write_menu ua, es, teacher_curriculums_path, 'カリキュラムを設定します'

メニュー表示用のヘルパメソッドの実装

上記の機能を実現するためのヘルパメソッドのテストを spec/helpers/application_helper_spec.rb に記述する。まずは、メニューのタイトルを返すメソッド menu_title のテストである。

  it 'should obtain menu title' do
    expect(menu_title).to eq '<tr><th>リンク</th><th>内容</th><th>権限</th></tr>'
  end

この実装を app/helpers/application_helper.rb に記述する。inline HAML をしてもよいのだがログが膨大になった覚えがあるので、懐かしく content_tag で作成する。以前は文字列で出力して html_safe などをしていたのだが、view の concat を使うとよいらしい(Rails3系でcontent_tagのネストを行う。array を引数配列にし、さらに中で flatten しているのは後でこのメソッドを使い回すからである。

  # @param [Array<String>] メニュータイトルの翻訳キーの配列
  # @return [String] メニューのタイトルコンテンツを得る
  def menu_title(*array)
    array = %w( リンク 内容 権限 ) if array.length == 0
    content_tag :tr do                     
      array.flatten.each do |str| 
        concat content_tag(:th, str)
      end                         
    end
  end

同様に、メニューの中身を記述するメソッドのテストを行う。ユーザ、セッション、path、説明文を送ると権限に応じてリンクをオン・オフしてくれる。また、権限列には昨日の権限文字列が表示される。ここでは、権限がある場合とない場合の両方のテストを実施している。

  it 'should obtain a table line' do
    kyoumu = user_factory :skyoumu    ans = write_menu(kyoumu, { 'is_kyoumu?' => true }, teacher_kyoumus_path, 'abc')
    expect(ans).to eq '<tr><td><a href="/teacher/kyoumus">[教務室トップページ]</a></td><td>abc</td><td>教務室</td></tr>'      
    staff = user_factory :hkob                
    ans = write_menu(staff, {}, teacher_kyoumus_path, 'abc')
    expect(ans).to eq '<tr><td>教務室トップページ</td><td>abc</td><td>教務室</td></tr>'
  end

この実装は以下の様になる。

  # @param [User,Admin] ua user または admin
  # @param [Hash] es user_session または admin_session
  # @param [String] path URL
  # @param [String] detail 説明文字列
  # @param [String] method GET 以外の場合は記述
  # @return [String] 表示するメニュー行
  def write_menu(ua, es, path, detail, method = nil)
    privileges = privileges_from_path(path)
    can_link = ua.has_privileges?(es, privileges)
    content_tag :tr do
      concat content_tag(:td, link_to_if(can_link, lh(title_from_path(path), can_link), path))
      concat content_tag(:td, detail)
      concat content_tag(:td, privileges_str(privileges))
    end
  end

ブラウザでの描画結果

ブラウザでのイメージはこんな感じになる。現在、教務室でログインしているので、教務室メニューが最初に表示される。カリキュラム設定以外は、まだ未修正である。

学生室メニューのタブをクリックすると中身が学生室のメニューになる。こちらもリンク先は未設定である。

画面が狭い場合には自動的にタブが二段になる。今までのように表示がかぶるようなことはない。

今日はここまで。