Motomichi Works Blog

モトミチワークスブログです。その日学習したことについて書いている日記みたいなものです。

vue.js 2.x その0004 アコーディオンを作る(jQueryのslideToggleみたいなものを作る)

2018年03月22日追記

アコーディオンひとつ分をコンポーネント化して、コンポーネント内だけで状態管理する方が良いので書き直しました。

vue.js 2.x その0004-02 アコーディオンを作る(jQueryのslideToggleみたいなものを作る) - Motomichi Works Blog

サンプルコード(v-forで出力した要素にそれぞれ)

v-forで出力した要素それぞれにアコーディオンを入れるイメージで、以下のように書いてみました。

<style>
.accordion{}
.accordion__trigger{}
.accordion__contents-outer-wrap{
  transition: max-height 2s;
  max-height: 0;
  overflow: hidden;
  background: #eeeeff;
}
.accordion__contents-inner-wrap{
  padding: 100px 10px;
}
</style>

<div id="accordion-app">
  <ul>
    <li class="accordion" v-for="(item, index) in items">
      <button class="accordion__trigger" v-on:click="slideToggle(index)">
        accordion-trigger-{{index}}
      </button>
      <div class="accordion__contents-outer-wrap" v-bind:style="{ maxHeight: items[index].contents.maxHeight + 'px' }">
        <div class="accordion__contents-inner-wrap" ref="contentsInner">
          {{item.title}}<br>
          {{item.contents.lead}}
        </div>
      </div>
    </li>
  </ul>
</div>

<script src="js/vue.js"></script>
<script>
  var items = [
    {
      title: 'content-0',
      contents: {
        lead: 'lead-0',
        isActive: false,
        maxHeight: 0,
      }
    },
    {
      title: 'content-0',
      contents: {
        lead: 'lead-0',
        isActive: false,
        maxHeight: 0,
      }
    },
  ];

  var vueApp = new Vue({
    el: '#accordion-app',
    data: {
      items: items,
    },
    methods: {
      slideToggle: function(index){
        this.items[index].contents.isActive = !this.items[index].contents.isActive;
        if(this.items[index].contents.isActive){
          this.items[index].contents.maxHeight = this.$refs.contentsInner[index].clientHeight;
        }else{
          this.items[index].contents.maxHeight = 0;
        }
      },
    },
  });
</script>

サンプルソース(table-rowを開閉)

trタグの開閉も書いてみましたがHTMLタグが多くなりますね。 display: table-rowでも同じですね。

<style>
.container{
  width: 600px;
}
table{
  width: 100%;
  border-collapse: collapse;
  border-spacing: 0;
}
.contents-wrap{
  padding: 10px;
  background: #eeeeff;
}
tr + tr .contents-wrap{
  border-top: 1px solid #aaaaaa;
}
th,td{
  border: 0;
  padding: 0;
}
.accordion{
  transition: max-height 2s;
  max-height: 0;
  overflow: hidden;
}

</style>

<div id="vue-app">
  <div class="container">
    <table>

      <tr>
        <th>
          <div>
            <div class="contents-wrap">
              ラジオボタンrow
            </div>
          </div>
        </th>
        <td>
          <div>
            <div class="contents-wrap">
              <span class="controll">
                <input type="radio" name="hoge-radio" value="true" v-model="hoge">
                <label for="">開く</label>
              </span>
              <span class="controll">
                <input type="radio" name="hoge-radio" value="false" v-model="hoge">
                <label for="">閉じる</label>
              </span>
            </div>
          </div>
        </td>
      </tr>

      <tr>
        <th>
          <div class="accordion" :style="{'max-height': state.hoge.maxHeight + 'px'}">
            <div class="contents-wrap" ref="hoge">
              トグルrow
            </div>
          </div>
        </th>
        <td>
          <div class="accordion" :style="{'max-height': state.hoge.maxHeight + 'px'}">
            <div class="contents-wrap">
              トグルコンテンツ
            </div>
          </div>
        </td>
      </tr>

      <tr>
        <th>
          <div>
            <div class="contents-wrap">
              ラジオボタンrow
            </div>
          </div>
        </th>
        <td>
          <div>
            <div class="contents-wrap">
              <span class="controll">
                <input type="radio" name="foo-radio" value="true" v-model="foo">
                <label for="">開く</label>
              </span>
              <span class="controll">
                <input type="radio" name="foo-radio" value="false" v-model="foo">
                <label for="">閉じる</label>
              </span>
            </div>
          </div>
        </td>
      </tr>

      <tr>
        <th>
          <div class="accordion" :style="{'max-height': state.foo.maxHeight + 'px'}">
            <div class="contents-wrap" ref="foo">
              トグルrow
            </div>
          </div>
        </th>
        <td>
          <div class="accordion" :style="{'max-height': state.foo.maxHeight + 'px'}">
            <div class="contents-wrap">
              トグルコンテンツ
            </div>
          </div>
        </td>
      </tr>

    </table>
  </div>
</div>

<script src="js/vue.js"></script>
<script>
  var vueApp = new Vue({
    el: '#vue-app',
    data: {
      hoge: 'false',
      foo: 'false',
      piyo: 'false',
      state: {
        hoge: {
          isVisible: false,
          maxHeight: 0,
        },
        foo: {
          isVisible: false,
          maxHeight: 0,
        },
      }
    },
    watch: {
      hoge: function(val){
        this.slideToggle('hoge', val);
      },
      foo: function(val){
        this.slideToggle('foo', val);
      },
    },
    methods: {
      slideToggle: function(key, val){
        this.state[key].isVisible = !this.state[key].isVisible;

        if(this.state[key].isVisible){
          this.state[key].maxHeight = this.$refs[key].clientHeight;
        }else{
          this.state[key].maxHeight = 0;
        }
        console.log(this.state[key]);
      },
    },
  });
</script>