vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

JavascriptでTab切り替え

諸事情によりめっきりブログ作成が滞っていますが...。

今日はJavascriptCSSを使って、Tabを切り替えてみます。

やったこと

  • 5つのタブと左右に1つずつボタンを作成
  • タブのうち2つを非表示にして、左右のボタンで切り替える
  • タブをクリックしたらページ遷移する

ページ構成

ほぼいつもと同じですが、CoffeeScriptからJavascriptに変更しています。CoffeeScriptのサンプルが少なく、むしろ書くのに時間がかかってしまうので。

Lapp.rb
Lconfig.ru
LGemfile
Lvendor
    Lbundle
        L...
Lpublic
    Ljs
        Ljavascript.js
Lviews
    Llayout.slim
    LviewTab.slim
    Lcss
        Lstylesheet.sass

ボタンとタブを作る

ボタンは「input type="button"」、タブは「input type="radio"」で作成します。

タブにラジオボタンを使うのは、タブをクリックすると、他のタブを無選択状態にする、という実装が楽にできるからです。

slim(HTML)

viewTab.slim

section#tab_frame
    input#arrow_button_left type="button" onclick="moveTabLeft();"
        label#arrow_label for="arrow_button_left" <
    input#tab_0 type="radio" name="func" onchange="movePage(0);"
        label#tab_label_0 for="tab_0" タブ1
    input#tab_1 type="radio" name="func" onchange="movePage(1);"
        label#tab_label_1 for="tab_1" タブ2
    input#tab_2 type="radio" name="func" onchange="movePage(2);"
        label#tab_label_2 for="tab_2" タブ3
    input#tab_3 type="radio" name="func" onchange="movePage(3);"
        label#tab_label_3 for="tab_3" タブ4
    input#tab_4 type="radio" name="func" onchange="movePage(4);"
        label#tab_label_4 for="tab_4" タブ5
    input#arrow_button_right type="button" onclick="moveTabRight();"
        label#arrow_label for="arrow_button_right" >

ボタン、ラジオボタンCSSを適用する

inputタグを使うときの問題は、UI変更などのcssが効かないところです。

そこで、inputタグで表示されるもの自体はどこかにやってしまって、labelを使ってボタンの代わりにします。

Labelを使う

inputに付与したidを「for」に設定することで、inputで表示されるボタン以外にlabelもボタンと同じように動作するようになります。

inputタグで表示されるものをどこかにやる

css(sass)で、画面左上に移動させます。

stylesheet.sass

#arrow_button_left
    top: -999px
    left: -999px
    position: absolute
#arrow_button_right
    top: -999px
    left: -9999px
    position: absolute
input[type="radio"]
    top: -999px
    left: -999px
    position: absolute

上記はidで指定していますが、3つ目のようにinput[type="radio"]のように指定すれば該当のものに一括で設定することができます。
ただ、cssで指定するときに、「index」や「div」のように指定するよりidやclassを指定する方が速い、という話を聞いたのでidで指定してみました。

ラベルのUI設定

こんな感じです。

stylesheet.sass

#tab_frame
    height: 50px
    width: 600px
#arrow_label
    background-color: #FFF
    height: 100%
    width: 20px
    border: 1px solid #000
    border-radius: 5%
    display: block
    float: left
input[type="button"]:disabled + #arrow_label
    background-color: #111
    height: 100%
    width: 20px
    border: 1px solid #FFF
    border-radius: 5%
    display: block
    float: left
#tab_label_0
    background-color: #FFF
    height: 100%
    width: 140px
    border: 1px solid #000
    border-radius: 5%
    float: left
#tab_0:checked + #tab_label_0
    background-color: #000
    height: 100%
    width: 140px
    border: 1px solid #000
    border-radius: 5%
    float: left
#tab_label_1
    background-color: #FFF
    height: 100%
    width: 140px
    border: 1px solid #000
    border-radius: 5%
    float: left
#tab_1:checked + #tab_label_1
    background-color: #000
    height: 100%
    width: 140px
    border: 1px solid #000
    border-radius: 5%
    float: left
#tab_label_2
    background-color: #FFF
    height: 100%
    width: 140px
    border: 1px solid #000
    border-radius: 5%
    float: left
#tab_2:checked + #tab_label_2
    background-color: #000
    height: 100%
    width: 140px
    border: 1px solid #000
    border-radius: 5%
    float: left
#tab_label_3
    background-color: #FFF
    height: 100%
    width: 140px
    border: 1px solid #000
    border-radius: 5%
    float: left
#tab_3:checked + #tab_label_3
    background-color: #000
    height: 100%
    width: 140px
    border: 1px solid #000
    border-radius: 5%
    float: left
#tab_label_4
    background-color: #FFF
    height: 100%
    width: 140px
    border: 1px solid #000
    border-radius: 5%
    float: left
#tab_4:checked + #tab_label_4
    background-color: #000
    height: 100%
    width: 140px
    border: 1px solid #000
    border-radius: 5%
    float: left

inputタグが特定の状態の時に、Labelにcssを適用する

inputのidに「:checked」や「:disabled」を付けると、該当する状態になったときにcssが適用されます。
で、それをLabelに適用するために後ろに「+(Labelの)id」を指定します。

ボタン・タブの表示制御

javascriptを使って、以下の動作を実現します。 * タブを2つ非表示にする * 左のボタンを押すと左にタブを一つ移動し、右のボタンで右のタブに移動する * 最初のタブが表示されたら左のボタンをDisableにし、最後のタブが表示されたら右のボタンをDisableにする

準備

タブの切り替えでページ遷移をするため、ページロード完了後に該当タブ(ラジオボタン)のステータスを「checked」にし、その他のタブの表示位置やボタンのEnabled・Disabledをセットするjavascriptメソッドを呼びます。
詳しい説明はコードの後に記載します。

app.rb

require 'sass'
require 'sinatra'
require 'sinatra/base'
require 'sinatra/reloader'
require 'slim'

class MainApp < Sinatra::Base
    configure :development do
        register Sinatra::Reloader
    end
    get '/' do
        @intTabNum = params[:tab].to_i
        @intLastRightTabNum = params[:lrt].to_i
        # 右のタブ番号の最低値は2
        if @intLastRightTabNum < 2
            @intLastRightTabNum = 2
        end
        @updateTabs = "loadTabs(" + @intTabNum.to_s + ", " + @intLastRightTabNum.to_s + ");"
        slim :viewTab
    end
    get '/css/stylesheet.css' do
        sass :'css/stylesheet'
    end
end

layout.slim

doctype html
html
    head
        meta charset="utf-8"
        title TabTest
        link href="/css/stylesheet.css" rel="stylesheet"
    body onload==@updateTabs
        == yield
        script src="/js/javascript.js"

javascript.js

(function() {
    var intTabNum = 0;
    var aryTabName = ["tab_label_0", "tab_label_1", "tab_label_2", "tab_label_3", "tab_label_4"]
    var intRightTabNum = 2;
    var intLeftTabNum = 0;
    var intDefaultRightTabNum = 2;
    this.loadTabs = function(intNewTabNum, intLastRightTabNum){
        // ロード時に実行. Tabの表示切り替え、矢印ボタンのEnable/Disable.
        intTabNum = intNewTabNum;
        intRightTabNum = intLastRightTabNum;
        updateTabStyles();
    };
    this.moveTabRight = function(){
        // 一つ右隣のTabを表示.
        if(intRightTabNum <= 3){
            intRightTabNum++;
        }
        updateTabStyles();
    };
    this.moveTabLeft = function(){
        // 一つ左隣のTabを表示.
        if(intRightTabNum > 0){
            intRightTabNum--;
        }
        updateTabStyles();
    };
    this.updateTabStyles = function(){
        // 矢印ボタンのEnable/Disableを設定.
        if(intRightTabNum <= intDefaultRightTabNum){
            document.getElementById("arrow_button_left").disabled = "disabled";
            document.getElementById("arrow_button_right").disabled = "";
        }else if(intRightTabNum >= 4){
            document.getElementById("arrow_button_left").disabled = "";
            document.getElementById("arrow_button_right").disabled = "disabled";
        }else{
            document.getElementById("arrow_button_left").disabled = "";
            document.getElementById("arrow_button_right").disabled = "";
        }
        // Tabの表示切り替え
        document.getElementById("tab_" + intTabNum).checked = true;
        intTabLeftNum = intRightTabNum - 2;
        for(var i=4;i>=0;i--){
            if((i >= intTabLeftNum)&&(i <= intRightTabNum)){
                document.getElementById(aryTabName[i]).style.display = "block";
            }else{
                document.getElementById(aryTabName[i]).style.display = "none";
            }
        }
    };
    this.movePage = function(intNewPageNum){
        // タブの番号と、次のページに遷移する. 一番右に表示されるタブ番号をパラメータとして付与.
        location.href='?tab=' + intNewPageNum + '&lrt=' + intRightTabNum;
    };
}).call(this);

ボタンのEnabled・Disabled切り替え

javascriptからHTMLの要素にアクセスするには、「document.getElementById」を使います。

ボタンをDisabledにするには、以下のコードを実行します。

document.getElementById("input type="button"のID").disabled = "disabled";

空にすれば再びEnabledになります。

document.getElementById("input type="button"のID").disabled = "";

ラジオボタンにチェックを入れる

document.getElementById("input type="radio"のID").checked = true;
  • 明示的にチェックを外すにはfalseを渡しますが、今回のように常にどれか一つが選択されている状態を実現するなら、上記コードを実行すれば他は自動でfalseになります。

ラベルの表示・非表示を設定する

今回のコードでは、inputタグを非表示にしても関連させたラベルは表示され続けます(というか、labelタグがinputタグの中に入らない)。
そのため、ラベルを非表示にして対応します。

// ラベルを表示する
document.getElementById("labelのID").style.display = "block";
// ラベルを非表示にする
document.getElementById("labelのID").style.display = "none";

ページ遷移時に、表示していたタブの位置を残す

上記までの処理で、クリックしたタブ(ラジオボタン)が「checked」となり、該当のページに遷移するところまで実現できます(多分)。

問題は、ページ遷移のたびに移動させたタブの位置がデフォルトと同じ状態に戻ってしまうことです。

原因はページロード時にjavascriptの変数がリセットされてしまうためですが、これをページ遷移時にパラメータを渡して解決してみました。

this.movePage = function(intNewPageNum){
    // タブの番号と、次のページに遷移する. 一番右に表示されるタブ番号をパラメータとして付与.
    location.href='?tab=' + intNewPageNum + '&lrt=' + intRightTabNum;
};

この部分で、タブ(ラジオボタン)のonchangedから呼び出します。

で、controllerにてパラメータを取り出し、bodyタグのonloadからjavascriptにそれぞれの値を渡す、という流れになっています。

感想など

今回のコードは、ページ遷移なしでタブ表示やボタンを操作するためにcontroller(app.rb)ではなくjavascriptで大部分の処理を行っています。
いっその事全部javascriptでやった方がいいのかなぁ、とも思ったりしますが、この辺の振り分け方がいまいちしっくりこない感じがします。

この辺りはいろいろ作りながら調整していくしかないかな、というところです。

参考

ボタンの無効・有効切り替え

radioボタンをタブとして扱う