TreeViewコンポーネント(Vuetify)の「Async items」機能について


背景

Vuetifyの公式サイトを見ていて、treeviewコンポーネントに「Async items」という機能があるのを見つけたので試してみた。
https://vuetifyjs.com/ja/components/treeview/

簡単に説明すると、ツリーのノードを開いた瞬間に子要素のデータを取得しに行く機能。
まぁ、ツリーのコンポーネントなら普通は持ってるよねって感じの機能ですが、使ってみると色々見えてきたりするので。。。

あと、今回はローカルファイルからのデータ読み込みをやるので、ついでに、VS CodeのLive Serverを試してみようと思う(実はこっちが本当の目的だったりする)。

ツリーコンポーネントへの「Async items」の適用

以前作成したWebComponents用ツリーコンポーネントのサンプルは、コード内にデータをべた書きしていたので、これを他からデータを取得してくるように変えてみる。

準備

以前作成したWebComponents用ツリーコンポーネントのコードを落としてくる。

> git clone https://github.com/yusuke-ka/sample-vce-vuetify.git

インストール。

> cd sample-vce-vuetify
> yarn install

元のコードは下のようになっていて、データ(items)は固定になっている。

tree.vue
<template>
  <div id="app">
    <v-app id="inspire">
      <v-treeview selectable :items="items"></v-treeview>
    </v-app>
  </div>
</template>

<script>
...
export default {
  el: "#app",
  vuetify: new Vuetify({
    icons: {
      iconfont: "md",
    },
  }),
  data: () => ({
    items: [
      {
        id: 1,
        name: "Applications :",
        children: [
          { id: 2, name: "Calendar : app" },
          { id: 3, name: "Chrome : app" },
          { id: 4, name: "Webstorm : app" },
        ],
      },
      ....
    ],
  }),
};
</script>

yarn buildでビルドすると、ツリーコンポーネントがWebComponents化されてbundle.jsが生成されて、他から呼び出せるようになる。

プロジェクト直下に下記のようなindex.htmlを置くと、ローカルでの動作確認ができる。

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Custom Element By Vue + Vuetify</title>
    <script src="dist/bundle.js"></script>
  </head>
  <body>
    <vce-tree></vce-tree>
  </body>
</html>

こんな感じ↓

これを、ノードを開くタイミングで子要素を取得してくるように変更してみる。

コード修正

まず、template部分に:load-childrenを追加。メソッド(算出プロパティ)名は「fetchData」にしておく。
また、メソッド名が紛らわしいので、「:items="items"」は「:items="itemList"」に変更しておく。

tree.vue
<template>
  <div id="app">
    <v-app id="inspire">
      <v-treeview
        selectable
        :items="itemList"
        :load-children="fetchData"
      ></v-treeview>
    </v-app>
  </div>
</template>

script部分は、dataitemsは空にしておき、computeditemList()を定義する。
さらに、methodsfetchData()を定義。ここでデータを取得する。
普通はバックエンドのサーバーからデータを取得するんだけど、今回はローカルファイルを指定。
あと、外部からデータを取得しているのを分かりやすくするために、最初にpauseを定義して、意図的に待ち時間を入れている。

tree.vue
<script>
...

const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

export default {
  ...
  data: () => ({
    items: [],
  }),
  computed: {
    itemList() {
      return [
        {
          name: "ItemList",
          children: this.items,
        },
      ];
    },
  },
  methods: {
    async fetchData(item) {
      await pause(1000); // 意図的に待ち時間を発生させる
      return fetch("./data.json")
        .then((res) => res.json())
        .then((json) => item.children.push(...json))
        .catch((err) => console.warn(err));
    },
  },
};
</script>

ローカルに置いたデータファイル(data.json)はこちら。

data.json
[
  {
    "id": 1,
    "name": "ChildA :",
    "children": [
      {
        "id": 4,
        "name": "ChildA-1 :"
      }
    ]
  },
  {
    "id": 2,
    "name": "ChildB :",
    "children": []
  },
  {
    "id": 3,
    "name": "ChildC :"
  }
]

vuetifytreeviewでは、「children」を空にしてやると、そのノードを開いた際に「:load-children」に定義したメソッドが実行されるらしいので、上のデータでは「ChildB」だけその状態にしている。

動作確認

このままビルドして、index.htmlを再表示してみる。

ローカルファイルを読み込んでいるので、最近のブラウザだとCORSのエラーが発生する。

ということで、予定通りVS CodeにLive Serverを入れて対応する。

Live Serverの導入

VS CodeのExtensions(MARKETPLACE)で、「Live Server」で検索

Live ServerのExtensionが表示されるので、「Install」ボタンからインストールする。

導入完了。

実行も簡単。
index.htmlをVS Code上で開いた状態で、右下にある「Go Live」を押す。

自動的にブラウザが立ち上がり、index.htmlが表示される。

動作確認(再)

初期表示では、「itemList()」の実行結果として、ルートノードが表示される。

ノードを開くと、fetchData(item)が実行されて、「data.json」に書いたデータが表示される。

続いて取得したデータの子要素を開いてみる。

ChildAの子要素「ChildA-1」は「data.json」に定義されているので、最初のデータ取得で一緒に取得され、即座に展開される。

ChildBの子要素は「data.json」には空で定義されているので、展開しようとした際にfetchData(item)が実行されて再度取得される。
(※本来は引数のitemで指定したノードの子要素を取りに行くべきだが、今回はitemを無視して「data.json」を読むので最初と全く同じデータが展開される。)

ChildCは「data.json」で「children」を指定していないので、そもそもノードが展開できない。

ということで、treeviewコンポーネントで動的に子要素を取得するように変更できました。

さいごに

ローカルで色々書いていると、CORSのエラーにはよく遭遇する。
今までは、わざわざS3でホスティングして確認していたので結構面倒だったが、Live Serverを使うことでだいぶ楽になることが分かった。

treeviewコンポーネントへの「Async items」導入については、「順次ロードさせるためにはchildrenを空にして返してやる必要がある」ってのが分かったことが収穫かな。(※まぁ公式サイトに書いてあるんですが。)