IMG_5256
Development

Vanilla JavaScriptを書いてみよう その3(タブUIを実装)

さて前回はJavaScriptで取得したhtml要素のclass属性を簡単に追加や削除が行えるように Element というクラスの実装を紹介しました。

今回はそのElementクラスをクラスを使ってタブUIを操作できるクラスを実装してみたいと思います。

まず部品として以下の2点が必要かと思いますので、それぞれに対応するクラス名称をつけました。

  • タブボタン => TabNavItem
  • タブ内容ボックス => TabContent

TabNavItem

TabNavItemの仕様として以下の項目があげられます

  1. 生のhtml要素を渡して初期化出来る
  2. 選択中に切り替えられる
  3. 非選択中に切り替えられる
  4. 生のhtml要素と比較して自分自身かどうかをチェックできる

1. 生のhtml要素を渡して初期化出来る

これはコンストラクタですね。new した時の初期化処理ですが、引数で受け取った生のhtml要素を前回作成した Element オブジェクトにして保持しておきます。
後は選択中を示すclass名もここで定義しておきました。

function TabNavItem(el) {
  this.el = new Vanilla.Element(el);
  this.current_class_name = 'tab-nav__item--current';
}

2. 選択中に切り替えられる

自身のElementオブジェクトに対して、選択中のclass名を持っていなければ追加します。

TabNavItem.prototype.toCurrent = function() {
  if (this.isCurrent()) return;
  this.el.addClass(this.current_class_name);
};

3. 非選択中に切り替えられる

自身のElementオブジェクトに対して、選択中のclass名を削除します。

TabNavItem.prototype.toUncurrent = function() {
  this.el.removeClass(this.current_class_name);
};

4. 生のhtml要素と比較して自分自身かどうかをチェックできる

実はこれもタブUIを実装する上で結構重要な機能です。
これはElementオブジェクトの持っている同名のメソッドに処理を渡しています。

TabNavItem.prototype.isEqual = function(targetElement) {
  return this.el.isEqual(targetElement);
};

TabNavItemの実装は以上です。まとめたコードがこちらです。

var TabNavItem = (function() {
  function TabNavItem(el) {
    this.el = new Element(el);
    this.current_class_name = 'tab-nav__item--current';
  }

  TabNavItem.prototype.toCurrent = function() {
    if (this.isCurrent()) return;
    this.el.addClass(this.current_class_name);
  };

  TabNavItem.prototype.toUncurrent = function() {
    this.el.removeClass(this.current_class_name);
  };

  TabNavItem.prototype.isCurrent = function() {
    this.el.hasClass(this.current_class_name);
  };

  TabNavItem.prototype.isEqual = function(targetElement) {
    return this.el.isEqual(targetElement);
  };

  return TabNavItem;
})();

TabContent

TabContentは仕様として以下の項目があげられます。

  1. 生のhtml要素を受け取って初期化出来る
  2. 選択中に切り替えられる
  3. 非選択中に切り替えられる

1. 生のhtml要素を受け取って初期化出来る

TabNavItemとほぼ同じですが、TabContentの場合はhtml要素のid属性を label というプロパティに保持しておきました。

function TabContent(el) {
  this.el = new Vanilla.Element(el);
  this.current_class_name = 'tab-content--current';
  this.label = this.el.id;
}

2. 選択中に切り替えられる

自身のElementオブジェクトに対して、選択中のclass名を持っていなければ追加します。

TabContent.prototype.toCurrent = function() {
  this.el.addClass(this.current_class_name);
};

3. 非選択中に切り替えられる

自身のElementオブジェクトに対して、選択中のclass名を削除します。

TabContent.prototype.toUncurrent = function() {
  this.el.removeClass(this.current_class_name);
};

TabContentの実装は以上です。まとめたコードはこちら

TabContent = (function() {
  function TabContent(el) {
    this.el = new Element(el);
    this.current_class_name = 'tab-content--current';
    this.label = this.el.id;
  }

  TabContent.prototype.toCurrent = function() {
    this.el.addClass(this.current_class_name);
  };

  TabContent.prototype.toUncurrent = function() {
    this.el.removeClass(this.current_class_name);
  };

  return TabContent;
})();

イベントに紐付けて動かしてみる

最低限の役者が揃いましたので実際に動す為のコードを書いてみたいと思います。
まずはなにはともあれDOMが構築されたタイミングを取れるようにwindowのloadイベントにハンドラをあててからの作業とですね。

addEvent(window, 'load', function() {
  // 各部品のhtml要素を取得
  var tabNav = document.querySelector('.tab-nav');
  var tabNavItemElements = var tabNav.querySelectorAll('.tab-nav__item');
  var tabContentElements = document.querySelector('.tab-contents').querySelectorAll('.tab-content');

  // 格納用の配列を初期化
  var tabNavItems = [];
  var tabContents = [];

  // TabNavItem と TabContent のインスタンスを生成
  for (var i in tabNavItemElements) {
    // 配列のプロパティ length を取り出してしまわないようにチェック
    if (typeof tabNavItemElements[i] === 'object') {
      tabNavItems.push(new TabNavItem(tabNavItemElements[i]));
    }
    if (typeof tabContentElements[i] === 'object') {
      tabContents.push(new TabContent(tabContentElements[i]));
    }
  }

  // タブボタンのクリックイベントを一気に登録する
  addEvent(tabNav, 'click', function(e) {
    // ループ中で使う変数を用意
    var tabNavItem = null;
    var tabContent = null;

    // クリックしたタブボタンのhref属性を取得
    var label = e.srcElement.href.replace(/^.+#/, '');

    // クリックしたタブボタンのli要素を取得
    var clickedItem = e.srcElement.parentNode;

    // 全TabNavItemを回しながら選択状態を切り替えていく
    for (var i in tabNavItems) {
      tabNavItem = tabNavItems[i];
      if (tabNavItem.isEqual(clickedItem)) {
        tabNavItem.toCurrent();
      } else {
        tabNavItem.toUncurrent();
      }

      // TabContentの数も一緒なので同じループで選択状態を切り替えていく
      tabContent = tabContents[i];
      if (tabContent.label === label) {
        tabContent.toCurrent();
      } else {
        tabContent.toUncurrent();
      }
    }
  });
});

以上でタブUIの全体の実装が完了です。

実際に動作するサンプルがこちらです。

ポイントは部品ごとにクラス化しておくこと、イベントハンドリングは全てのボタンに割り当てるのではなく親要素にあてておく、見た目の切り替えはcssで書いてjsはclass属性の付け替えなどにとどめておくこと等です。

標準