拡張性に乏しいJavaScriptコード
【1】複数のボタンを内包するモーダルウィンドウ
モーダルウィンドウの中に複数のボタンを表示し、各ボタンには異なる役割(機能)が割り当てられているとする【1】。
各役割はJavaScriptの関数として用意するとした場合、JavaScript初級者なら【2】のようなJavaScriptコードを考えるのではないだろうか。
- var button1 = document.getElementById('button1');
var button2 = document.getElementById('button2');
var button3 = document.getElementById('button3');
button1.addEventListener('click', func1, false);
button2.addEventListener('click', func2, false);
button3.addEventListener('click', func3, false);
【2】id属性を利用して複数のボタンにそれぞれ異なる機能を割り当てるコード例
このコードでは、getElementByIdによってユニークなid属性を持つ要素を特定することで、個別に関数呼び出しを行っている。
ボタンが3個程度であれば、これでもよい。しかし、あらかじめ多くのボタンを用意しなければならない場合、さらにボタンが追加されることが想定されるようなケースでは、このようなコードは汎用性・拡張性に乏しいといえる。
前回同様、HTML、CSS、JavaScriptの各役割を考えて、できることは互いに任せることが重要である。
複数のボタンごとに呼び出す関数を自動セット
まずは【3】のようなHTMLがあるとしよう。
- <button data-role="confirm">確認</button>
<button data-role="submit">購入</button>
<button data-role="cancel">キャンセル</button>
<button data-role="close">閉じる</button>
<button data-role="back">戻る</button>
【3】独自データ属性を使って、各button要素に異なる役目を定義したHTMLコード例
button要素には、HTML5で利用できるようになった独自データ属性「data-」を使って、JavaScript側で関数として用意するメソッドに対応した属性値を与えている。
これに対して、JavaScriptでは【4】のようなコードを用意する。
- var elements = document.querySelectorAll('[data-role]');
for (var i = 0, len = elements.length; i < len; i++) {
var role = elements[i].getAttribute('data-role');
//Androidではdatasetが使えないため、getAttributeを使用
elements[i].addEventListener('click', methods[role], false);
}
var methods = {
confirm: function(){},
submit: function(){},
cancel: function(){},
close: function(){},
back: function(){}
};
【4】独自データ属性を利用して各button要素に固有の機能を割り当てるJavaScriptコード例
このようにボタンに対応する関数の呼び出しをHTMLコードの属性値で結びつけることで、固有のボタンとそのメソッド名を自動的に取得し、
clickイベントで各自関数を呼び出すように自動化している。
これにより、ボタンの増減、変更などがあった場合も最小限のコード修正で対応することが可能だ。
delegateを利用した複数オブジェクトの制御
では、複数のボタンとそれを表示する複数のモーダルウィンドウを表示しなければならないケースはどうだろう。
たとえば、そういったモーダルウィンドウがふたつの場合、【5】のようなJavaScriptコードが思いつく。
- var button1 = document.getElementById('button1');
var button2 = document.getElementById('button2');
var modal1 = document.getElementById('modal1');
var modal2 = document.getElementById('modal2');
button1.addEventListener('click', function() {
modal1.style.display = 'block';
}, false);
button2.addEventListener('click', function() {
modal2.style.display = 'block';
}, false);
【5】id属性を利用して複数のモーダルウィンドウ(ボタンを含む)を表示するJavaScriptコード例
各モーダルウィンドウとボタンをユニークなid属性値で特定して表示/非表示を切り替えているが、もっと大量の要素を制御する必要がある場合は非効率的だ。
前述のようにループ処理によって、個別要素を配列に格納して処理するというのもスマートではない。
そこで利用したいのがclass名とdelegateによる処理だ。
まず、【6】のようなHTMLとCSSがあるとしよう。
- <div id="container">
<div class="modal1">モーダルウィンドウ1</div>
<div class="modal2">モーダルウィンドウ2</div>
</div>
<button data-target-scene="scene-modal1">ボタン1</button>
<button data-target-scene="scene-modal2">ボタン2</button>
- .modal1, .modal2 {display: none;}
.scene-modal1 modal1, .scene-modal2 .modal2 {display: block;}
【6】複数のモーダルウィンドウ(ボタン含む)を表示するコード例。シーンを定義してCSSで表示/非表示を切り替えることを想定している。
これに対して、JavaScript側では【7】のようなコードを用意する(jQueryを利用している)。
- var container = $('#container');
$(document.body).delegate('[data-target-scene]', 'click', function() {
container.addClass($(this).attr('data-target-scene');
});
【7】定義したシーン(class名)でモーダルウィンドウの表示/非表示を切り替えるJavaScriptコード例
このJavaScriptコードでは、delegeteによって各要素で発生したclickイベントをbody要素に委譲したうえで、各要素に設定した独自データ属性「data-target-scene」の値を利用することで表示を切り替えている。
id属性で個別に記述するのではなく、またループ処理を回す必要もなく、たったこれだけのコードで大量のモーダルウィンドウとボタンを制御できてしまう。
delegateについて
delegateはイベントハンドリング関数のひとつで、内包するある子要素に発生したイベントを親要素に委譲して、親要素がイベントの発生を取得できるようにする。
このとき、イベントがDOMツリーを祖先要素に向かって伝播していくことになり、これをイベントバブリングと呼んでいる。
つまり、このバブリングの仕組みを利用して、実際にイベントを補足したい要素の祖先要素でイベントを捕捉して、なんらかの関数を呼び出すといった仕組みを実装できるのがdelegateである。
まとめ
2回に渡ってフロントエンド実装現場での具体的なコード事例を掲載してきたが、これらは初級エンジニアから中級エンジニアになるためには必須と思われるコーディング上の工夫だ。
スマートフォン向けのコンテンツ開発を考えた場合、実際にはAndroidの実装状況により、また利用できないAPIなどもあり、PC向けとは異なる制約もあるが、今後OSの進化とともにより汎用性が高く効率的なコーディングが可能となるはずだ。
そういった状況になることを踏まえて、現時点からHTML、CSS、JavaScriptの各役割分担を考慮したコード設計に慣れておくことが大切である。