cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - ActiveLovehtmlicon-new-collectionicon-personicon-teamlog-outoctocatpop-outspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

            
              <div id="filter-ui" class="pane">
  <div>
    <h2>Tabs</h2>
    <div class="pane">
      <ul>
        <li><a href="#" class="js-tab is-current" data-category="all">All</a></li>
        <li><a href="#" class="js-tab" data-category="alpha">Category Alpha</a></li>
        <li><a href="#" class="js-tab" data-category="beta">Category Beta</a></li>
        <li><a href="#" class="js-tab" data-category="3">Category 3</a></li>
        <li><a href="#" class="js-tab" data-category="4">Category 4</a></li>
        <li><a href="#" class="js-tab" data-category="5">Category 5</a></li>
      </ul>
      <ul>
        <li><a href="#" class="js-tab is-current" data-category="all">All</a></li>
        <li><a href="#" class="js-tab" data-category="alpha">Category Alpha</a></li>
        <li><a href="#" class="js-tab" data-category="beta">Category Beta</a></li>
        <li><a href="#" class="js-tab" data-category="3">Category 3</a></li>
        <li><a href="#" class="js-tab" data-category="4">Category 4</a></li>
        <li><a href="#" class="js-tab" data-category="5">Category 5</a></li>
      </ul>
    </div>

    <h2>Selects</h2>
    <select class="js-select">
      <option value="all">All</option>
      <option value="alpha">Alpha</option>
      <option value="beta">Beta</option>
      <option value="3">3</option>
      <option value="4">4</option>
      <option value="5">5</option>
    </select>
    <select class="js-select">
      <option value="all">All</option>
      <option value="alpha">Alpha</option>
      <option value="beta">Beta</option>
      <option value="3">3</option>
      <option value="4">4</option>
      <option value="5">5</option>
    </select>
    <select class="js-select">
      <option value="all">All</option>
      <option value="alpha">Alpha</option>
      <option value="beta">Beta</option>
      <option value="3">3</option>
      <option value="4">4</option>
      <option value="5">5</option>
    </select>
  </div>

  <div>
    <h2>Items</h2>
    <ol class="items">
      <li class="js-item" data-category="3">Item Category 3</li>
      <li class="js-item" data-category="3">Item Category 3</li>
      <li class="js-item" data-category="alpha">Item Category Alpha</li>
      <li class="js-item" data-category="beta">Item Category Beta</li>
      <li class="js-item" data-category="4">Item Category 4</li>
      <li class="js-item" data-category="beta">Item Category Beta</li>
      <li class="js-item" data-category="3">Item Category 3</li>
      <li class="js-item" data-category="alpha">Item Category Alpha</li>
      <li class="js-item" data-category="beta">Item Category Beta</li>
      <li class="js-item" data-category="3">Item Category 3</li>
      <li class="js-item" data-category="beta">Item Category Beta</li>
      <li class="js-item" data-category="alpha">Item Category Alpha</li>
      <li class="js-item" data-category="alpha">Item Category Alpha</li>
      <li class="js-item" data-category="alpha">Item Category Alpha</li>
      <li class="js-item" data-category="4">Item Category 4</li>
      <li class="js-item" data-category="3">Item Category 3</li>
      <li class="js-item" data-category="alpha">Item Category Alpha</li>
      <li class="js-item" data-category="beta">Item Category Beta</li>
      <li class="js-item" data-category="5">Item Category 5</li>
      <li class="js-item" data-category="5">Item Category 5</li>
      <li class="js-item" data-category="5">Item Category 5</li>
      <li class="js-item" data-category="4">Item Category 4</li>
    </ol>
  </div>

</div>
            
          
!
            
              * {
  transition: color .3s ease, opacity .3s ease;
}

.is-current {
  color: red;
}

.is-hidden {
  opacity: 0.3;
  // display: none;
}

.pane {
  display: flex;
  flex-flow: row nowrap;
}

.items {
  display: flex;
  flex-flow: column wrap;
  height: 75vh;
  list-style: none;
  padding: 0;
  
  > li {
    padding: 1em;
  }
}
            
          
!
            
              class FilterUI {

  constructor($container) {

    // ルート要素が無効または存在しないときはエラー
    if (typeof $container === 'undefined' || $container instanceof HTMLElement === false) {
      throw new Error('$container is not available!');
    }

    // ルート要素
    this.$container = $container;

    // タブ要素
    this.$tabs = null;

    // セレクト要素
    this.$selects = null;

    // リスト要素
    this.$items = null;

    // 現在のカテゴリ名
    this.currentCategory = '';

    // データ属性名
    this.dataKey = 'category';

    // タブ要素を検索するためのセレクタ名
    this.tabsSelector = '.js-tab';

    // セレクト要素を検索するためのセレクタ名
    this.selectsSelector = '.js-select';

    // リスト要素を検索するためのセレクタ名
    this.itemsSelector = '.js-item';

    // is-hiddenクラス
    this.hiddenClass = 'is-hidden';

    // is-currentクラス
    this.activeClass = 'is-current';

    // 「すべて」を意味するカテゴリ名
    this.allCategory = 'all';
  }

  // 初期化
  init() {
    // (最初の状態では、タブとセレクタは静的に辻褄が合っている前提がある)

    // ルート要素からタブ要素を取得する
    this.$tabs = this.$container.querySelectorAll(this.tabsSelector);

    // タブ要素にイベントリスナonClickを付与
    [].forEach.call(this.$tabs, ($tab) => {
      $tab.addEventListener('click', this.onClick.bind(this, $tab));
    });

    // ルート要素からselect要素を取得する
    this.$selects = this.$container.querySelectorAll(this.selectsSelector);

    // select要素にイベンリスナonChangeを付与
    [].forEach.call(this.$selects, ($select) => {
      $select.addEventListener('change', this.onChange.bind(this, $select));
    });
    
    // ルート要素からリスト要素を取得する
    this.$items = this.$container.querySelectorAll(this.itemsSelector);
  }

  // onClickイベントリスナ
  onClick($el, e) {
    e.preventDefault();

    // クリックされた要素のdata-categoryから次のカテゴリ名を取得する
    const nextCategory = $el.dataset[this.dataKey];

    // 取得した値で カテゴリ変更 を実行
    this.changeCategory(nextCategory);
  }

  // onChangeイベントリスナ
  onChange($el) {

    // 変更されたselect要素から現在のvalue値を取得する
    const nextCategory = $el.value;

    // 取得した値で カテゴリ変更 を実行
    this.changeCategory(nextCategory);
  }

  // カテゴリ変更
  changeCategory(nextCategory) {

    // (タブ側、セレクト側の両方から変更が発生することを想定しておく)

    // 次のカテゴリ名と現在のカテゴリ名を比較する
    // 次カテゴリ名と現在のカテゴリ名に差分がない場合は何もしない
    if (nextCategory === this.currentCategory) return;

    // 次のカテゴリ名を現在のカテゴリ名として保存する
    this.currentCategory = nextCategory;

    // タブの状態を更新する を実行
    this.updateTabs(nextCategory);

    // select要素の状態を更新する を実行
    this.updateSelects(nextCategory);

    // リストの状態を更新する を実行
    this.updateItems(nextCategory);
  }


  // タブの状態を更新する
  updateTabs(nextCategory) {

    // eachで要素ごとに繰り返し
    [].forEach.call(this.$tabs, ($tab) => {

      // タブ(個別)の状態を更新する を実行
      this.updateTab(nextCategory, $tab);

    });
  }

  // タブ(個別)の状態を更新する
  updateTab(nextCategory, $tab) {
  
    // すべての項目から .is-current を除去
    $tab.classList.remove(this.activeClass);

    // 次のカテゴリ名を持つ項目に .is-current をセット
    if ($tab.dataset[this.dataKey] !== nextCategory) return;
    $tab.classList.add(this.activeClass);

  }

  // select要素の状態を更新する
  updateSelects(nextCategory) {

    // eachで要素ごとに繰り返し
    [].forEach.call(this.$selects, ($select) => {

      // select要素(個別)の状態を更新する を実行
      this.updateSelect(nextCategory, $select);

    });
  }

  // select要素(個別)の状態を更新する
  updateSelect(nextCategory, $select) {

    // valueに次のカテゴリ名をセットする
    $select.value = nextCategory;
    
  }


  // リストの状態を更新する
  updateItems(nextCategory) {

    // eachで要素ごとに繰り返し
    [].forEach.call(this.$items, ($item) => {

      // リスト更新 を実行
      this.updateItem(nextCategory, $item);

    });

  }

  // リスト更新
  updateItem(nextCategory, $item) {

    // .is-hidden を除去
    $item.classList.remove(this.hiddenClass);

    // カテゴリ名が すべて のときはここで終了
    if (nextCategory === this.allCategory) return;

    // 対象物の data-category がカテゴリ名とマッチするかを比較
    // 一致しない場合は .is-hidden を付与
    if ($item.dataset[this.dataKey] === nextCategory) return;
    $item.classList.add(this.hiddenClass);

  }

}

const $el = document.getElementById('filter-ui');
const filterUi = new FilterUI($el);
filterUi.init();
            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.
Loading ..................

Console