JavascriptでTab切り替え
諸事情によりめっきりブログ作成が滞っていますが...。
今日はJavascriptとCSSを使って、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" >
- 基本的にはsectionタグの中にボタンとラジオボタンを放り込んでいるだけです。
- クリック・ラジオボタンのステータス変更でjavascriptのメソッドを呼びます。
ボタン、ラジオボタンに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でやった方がいいのかなぁ、とも思ったりしますが、この辺の振り分け方がいまいちしっくりこない感じがします。
この辺りはいろいろ作りながら調整していくしかないかな、というところです。