Motomichi Works Blog

その日学習したことについて書いている日記です。誰かの役に立ったらそれはそれで嬉しいです。

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

はじめに

以下の記事で書いたコードを少しマシな感じに書き直しました。

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

サーバーサイドから動的な値がwindow.itemsで書き出される想定で書いています。

rootコンポーネントから渡された値を更新したい場合は、rootコンポーネントが持っているstateを$emitなりで更新して、子供に渡しなおすと良さそうです。

そのとき、this.item.textをwatchしたり、computedの中で使うなどしないと子供のコンポーネントの状態は更新されないと思います。たぶん。

HTMLファイルサンプル

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Vue.jsのアコーディオンサンプル</title>
<meta charset="utf-8">
</head>
<body>
<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 is="my-component" v-for="(item, index) in state.items" :item="state.items[index]" :index="index">
    </li>
  </ul>
</div>

<!-- x-templateを定義 -->
<script type="text/x-template" id="my-component">
<li class="accordion">
  <button class="accordion__trigger" v-on:click="slideToggle()">
    accordion-trigger-{{index}}
  </button>
  <div class="accordion__contents-outer-wrap" v-bind:style="state.style">
    <div class="accordion__contents-inner-wrap" ref="contentsInner">
      text : {{ state.text }}<br>
      index : {{ index }}
    </div>
  </div>
</li>
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<script>
// サーバーから渡されたデータなどを想定
window.items = [
  { text: '何か個別に渡したいデータがあれば'},
  { text: '何か個別に渡したいデータがあれば'},
];

// my-componentを作成
Vue.component('my-component', {
  template: '#my-component',
  props: ['item', 'index'],
  data: function(){
    return {
      state: {
        isOpen: false,
        text: this.item.text,
        style: {},
      },
    };
  },
  methods: {
    slideToggle: function(){
      this.state.isOpen = !this.state.isOpen;
      this.setStyle(this.state.isOpen);
    },
    setStyle: function(isOpen){
      this.state.style = isOpen ? { 'maxHeight': this.$refs.contentsInner.clientHeight + 'px' } : {};
    },
  },
  mounted: function(){
    this.setStyle(this.state.isOpen);
  },
});

// VM
var vueApp = new Vue({
  el: '#accordion-app',
  data: {
    state: {
      items: window.items,
    },
  },
});
</script>
</body>
</html>