|||||||||||||||||||||

なんぶ電子

- 更新: 

Vue.jsインラインコンポーネントでフォルダツリー

Vue.js3でファイルツリー

前回はバージョン3になったVue.jsでインライン記述の方法を学びました。今回はそこから一歩踏み出して、インラインでコンポーネントを作成してみます。

公式ページにあるVue.js:「ツリー表示 の例」のバージョン2用のフォルダ構造表示のコードをバージョン3用に書き換えてみたいと思います。

バージョン2のコード

公式ページにあるバージョン2のコードは次のようになっています。

フォルダ構造表示(V2)


<!DOCTYPE html>
<html>
  <head>
    <title>Tree View</title>
    <script src="https://unpkg.com/vue@next"></script>
    <style> .bold { text-decoration: underline; } </style>

    <script type="text/x-template" id="item-template">
      <li>
        <div
          :class="{bold: isFolder}"
          @click="toggle"
          @dblclick="makeFolder">
          {{ item.name }}
          <span v-if="isFolder">[{{ isOpen ? '-' : '+' }}]</span>
        </div>
        <ul v-show="isOpen" v-if="isFolder">
          <tree-item
            class="item"
            v-for="(child, index) in item.children"
            :key="index"
            :item="child"
            @make-folder="$emit('make-folder', $event)"
            @add-item="$emit('add-item', $event)"
          ></tree-item>
          <li class="add" @click="$emit('add-item', item)">+</li>
        </ul>
      </li>
    </script>
  </head>
  <body>
    <p>(You can double click on an item to turn it into a folder.)</p>

    <!-- the demo root element -->
    <ul id="demo">
      <tree-item
        class="item"
        :item="treeData"
        @make-folder="makeFolder"
        @add-item="addItem"
      ></tree-item>
    </ul>

    <script>
      // demo data
      var treeData = {
        name: "My Tree",
        children: [
          { name: "hello" },
          { name: "wat" },
          {
            name: "child folder",
            children: [
              {
                name: "child folder",
                children: [{ name: "hello" }, { name: "wat" }]
              },
              { name: "hello" },
              { name: "wat" },
              {
                name: "child folder",
                children: [{ name: "hello" }, { name: "wat" }]
              }
            ]
          }
        ]
      };

      // define the tree-item component
      Vue.component("tree-item", {
        template: "#item-template",
        props: {
          item: Object
        },
        data: function() {
          return {
            isOpen: false
          };
        },
        computed: {
          isFolder: function() {
            return this.item.children && this.item.children.length;
          }
        },
        methods: {
          toggle: function() {
            if (this.isFolder) {
              this.isOpen = !this.isOpen;
            }
          },
          makeFolder: function() {
            if (!this.isFolder) {
              this.$emit("make-folder", this.item);
              this.isOpen = true;
            }
          }
        }
      });

      // boot up the demo
      var demo = new Vue({
        el: "#demo",
        data: {
          treeData: treeData
        },
        methods: {
          makeFolder: function(item) {
            Vue.set(item, "children", []);
            this.addItem(item);
          },
          addItem: function(item) {
            item.children.push({
              name: "new stuff"
            });
          }
        }
      });
    </script>
  </body>
</html>

Vue.jsバージョン2での記法

さしあたりバージョン2のコードでわかりづらい点を解説しておきます。コード内にアンダーラインを引いた箇所がそれにあたります。

  • script type="text/x-template"

    script type="text/x-template"の部分は、typeでtemplateの記述だと示したうえで、templateの中身をhtmlで記述しています。あとからここで設定してあるidを使って読みだすことができます。

  • :class="{ bold: isFolder }"

    isFolderが真だったら、boldというクラスが追加されるという記述方法です。別の方法として:class="[isFolder ? 'bold' : '']"という、三項演算子と配列を使った方法でも実現できます。またこちらのを使えばboldの部分は変数にもできます。

  • tree-item(head scriptタグ内)

    順番が逆転してしまいますが、あとでこの名前でコンポーネントを定義します。ここでは定義するはずの名前を使って再帰に使う記述になっています。

  • $emit

    子のコンポーネントからは親のデータを直接変更できないので、$emit(イベント名,パラメータ)で親にイベントを伝播するようにしています。親からはonイベント名="..."というかたちでキャッチできます。その際、子側の第二引数として設定したパラメータが親に渡されます。

  • tree-item(body ulタグ内)

    コンポーネント利用指定です。子の$emitで発生したイベントを受け取るために@make-folderと@add-itemのリスナを設定し、Vueのmethodに結びつけています。

  • treeData

    サンプル用の初期値です。nameで名前を指定します。フォルダの場合childrenという名前の配列を持ちその中に子を持ちます。

  • Vue.component

    ここではコンポーネントをグローバルに設定しています。templeteの箇所で、先のscript type="text/x-template"でidとして指定した"#item-template"を用いることで中身を読みだしています。ちなみにtemplateの部分に直接内容を文字列で記述することもできます。その際改行は使えません。改行を使いたい場合は`(バッククォート)で囲みます。

    もしローカルにコンポーネントを設定してしまった場合、再帰時には読み出すことができずエラーとなります。

  • computed:

    computedでディレクトリの構造が変わった場合に備えています。childrenという配列を持ち、その数が0でないときtrueとなります。

このコードの一番の肝は、最初に分かち書きとして<script type="text/x-template">にある

v-for="(child, index) in item.children" :item="child"

の部分です。親のdataにバインドしながら再帰構造を形成しています。

バージョン3への変換

上のコードをバージョン3用に変換すると次のようになります。

下線部が修正箇所です。

フォルダ構造表示(V3)


<!DOCTYPE html>
<html>
  <head>
    <title>Tree View</title>
    <script src="https://unpkg.com/vue@next"></script>
    <style>
      .bold { text-decoration: underline; }
    </style>
    <script type="text/x-template" id="item-template">
      <li>
        <div
          :class="[isFolder ? 'bold' : '']"
          @click="toggle"
          @dblclick="makeFolder">
          {{ item.name }}
          <span v-if="isFolder">[{{ isOpen ? '-' : '+' }}]</span>
        </div>
        <ul v-show="isOpen" v-if="isFolder">
          <tree-item
            class="item"
            v-for="(child, index) in item.children"
            :key="index"
            :item="child"
            @make-folder="$emit('make-folder', $event)"
            @add-item="$emit('add-item', $event)"
          ></tree-item>
          <li class="add" @click="$emit('add-item', item)">+</li>
        </ul>
      </li>
    </script>
  </head>
  <body>
    <p>(You can double click on an item to turn it into a folder.)</p>

    <ul id="demo">
      <tree-item
        class="item"
        :item="treeData"
        @make-folder="makeFolder"
        @add-item="addItem"
      ></tree-item>
    </ul>

    <script>
      // demo data
      var treeData = {
        name: "My Tree",
        children: [
          { name: "hello" },
          { name: "wat" },
          {
            name: "child folder",
            children: [
              {
                name: "child folder",
                children: [{ name: "hello" }, { name: "wat" }]
              },
              { name: "hello" },
              { name: "wat" },
              {
                name: "child folder",
                children: [{ name: "hello" }, { name: "wat" }]
              }
            ]
          }
        ]
      };

      var params = {
        data: function(){
          return { treeData: treeData };
        },
        methods: {
          makeFolder: function(item) {
            Vue.set(item, "children", []);
            this.addItem(item);
          },
          addItem: function(item) {
            item.children.push({
              name: "new stuff"
            });
          }
        }
      };
       
      const appli = Vue.createApp(params);
      
      appli.component("tree-item", {
        template: "#item-template",
        props: {
          item: Object
        },
        data: function() {
          return {
            isOpen: false
          };
        },
        computed: {
          isFolder: function() {
            return this.item.children && this.item.children.length;
          }
        },
        methods: {
          toggle: function() {
            if (this.isFolder) {
              this.isOpen = !this.isOpen;
            }
          },
          makeFolder: function() {
            if (!this.isFolder) {
              this.$emit("make-folder", this.item);
              this.isOpen = true;
            }
          }
        }
      });
      
      appli.mount("#demo");
      
    </script>
  </body>
</html>

筆者紹介


自分の写真
がーふぁ、とか、ふぃんてっく、とか世の中すっかりハイテクになってしまいました。プログラムのコーディングに触れることもある筆者ですが、自分の作業は硯と筆で文字をかいているみたいな古臭いものだと思っています。 今やこんな風にブログを書くことすらAIにとって代わられそうなほど技術は進んでいます。 生活やビジネスでPCを活用しようとするとき、そんな第一線の技術と比べてしまうとやる気が失せてしまいがちですが、おいしいお惣菜をネットで注文できる時代でも、手作りの味はすたれていません。 提示されたもの(アプリ)に自分を合わせるのでなく、自分の活動にあったアプリを作る。それがPC活用の基本なんじゃなかと思います。 そんな意見に同調していただける方向けにLinuxのDebianOSをはじめとした基本無料のアプリの使い方を紹介できたらなと考えています。

広告