💫

kintoneアプリの一覧画面をVue3+Quasarを使って表示

2025/02/09に公開

今度はVue3+Quasarを使ってkintoneの一覧画面を表示したいと思います。

Vuetify3のロードマップをみると開発が遅れているという感じなのと、Vuetify3だと表示の崩れが大きいのでこちらも作成してみました。

Vue2+Vuetify2やVue3+Vuetify3だけでは不明点が多かったですが、Quasarでも作成することによって何となく作成方法が掴めてきた気がします。

javascriptを使い始めて2年、ようやく初心者を抜け出せた気がします。

1. 利用環境

以下の環境で動作しています。

  • kintone … クラウド最新版(2025年2月時点)
  • Vue3 … バージョン3.5.13
  • Quasar … バージョン2.17.7

試しやすかったのでCDNから読み込む方式を選択しました。

2. 用意するもの

2-1. 一覧画面を作成

アプリ管理 → 一覧 → 「+」マーク から、新しい一覧をカスタマイズ形式で作成します。
本記事では以下のように設定します。

一覧名 レコード一覧の
表示形式
一覧を表示する範囲 ページネーションを
表示する
Vue3+Quasar一覧 カスタマイズ PC版のみで表示する チェックオフ

2-2. テストデータ

テストデータはこんな感じです。
既にQuasarのリセットCSSでどんな影響が出ているのかわかりますね笑
テストデータ

3. スクリプト全体

一覧に設定するHTMLと、JavaScript/CSSカスタマイズに設置するスクリプトを記載します。

3-1. HTML

「テスト一覧」のHTML部分
<div id="kintone-vue3-app">
  <q-layout>
    <q-page-container class="q-pa-md">
      <q-table
        :title="tableTitle"
        :rows="rows"
        :columns="columns"
        :loading="isLoading"
        separator="cell"
        row-key="$id.value"
      >
        <template v-slot:loading>
          <q-inner-loading showing color="primary"></q-inner-loading>
        </template>

        <template v-slot:top="props">
          <q-space></q-space>
          <q-btn
            flat round dense
            :icon="props.inFullscreen ? 'fullscreen_exit' : 'fullscreen'"
            @click="props.toggleFullscreen"
            class="q-ml-md"
          ></q-btn>
        </template>

        <template v-slot:header="props">
          <q-tr :props="props">
            <q-th auto-width></q-th>
            <q-th
              v-for="col in props.cols"
              :key="col.name"
              :props="props"
            >
              {{ col.label }}
            </q-th>
          </q-tr>
        </template>

        <template v-slot:body="props">
          <q-tr :props="props">
            <q-td>
              <q-btn
                unelevated
                round
                color="primary"
                @click="showDetail(props.row)"
              >
                <q-icon name="description"></q-icon>
              </q-btn>
            </q-td>
            <q-td
              v-for="col in props.cols"
              :key="col.name"
              :props="props"
            >
              {{ col.value }}
            </q-td>
          </q-tr>
        </template>
      </q-table>
    </q-page-container>
  </q-layout>
</div>

さきほど作成した「Vue3+Quasar一覧」のHTMLフィールドに本内容をコピペ。

3-2. JavaScript

次に「customizeView.js」という名称で以下の内容を保存します。

customizeView.js
((Vue,Quasar) => {
  'use strict'

  const targetViewId = 5536090

  const events = {
    index :[
      'app.record.index.show',
      'mobile.app.record.index.show',
    ]
  }

  let vObj = null;

  async function getRecords(appId) {
    const client = new KintoneRestAPIClient();
    const params = {
      app: appId,
    }
    return await client.record.getAllRecords(params)
    .then((resp) => {
      resp.forEach((item) => {
        item.hogehoge = 'テストデータですよー!'
      });
      return resp
    })
  }

  function generateView(appId) {

    const app = Vue.createApp({
      setup (props, context) {
        const $q = Quasar.useQuasar()

        const columnList = [
          { name: 'userName', align: 'center', label: 'ユーザー名', field: row => row.userName.value, sortable: true },
          { name: 'userID', align: 'center', label: 'ユーザーID', field: row => row.userID.value, sortable: true },
          { name: 'mailAddress', align: 'center', label: 'メールアドレス', field: row => row.mailAddress.value, sortable: true },
          { name: 'birthday', align: 'center', label: '生年月日', field: row => row.birthday.value, sortable: true },
          { name: 'hogehoge', align: 'center', label: 'ふがふが', field: row => row.hogehoge, sortable: true },
        ]

        const users = Vue.ref([]);
        function addUser(value) {
          users.value.push(value)
        }
        function addUsers(value) {
          users.value = value
        }

        const loadingValue = Vue.ref(true);
        function changeLoadingState(value) {
          loadingValue.value = value
        }

        function showDetail(item) {
          const url = `/k/${appId}/show#record=${item.$id.value}`
          window.open(url, '_blank')
        }

        function toggleFullScreen (e) {
          const target = e.target.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
  
          $q.fullscreen.toggle(target)
          .then(() => {
            console.info('fullscreen button push')
          })
          .catch((err) => {
            alert(err)
          })
        }

        Vue.onMounted(() => {
          console.info($q)
          console.info($q.platform)
        })

        return {
          tableTitle: '利用者一覧',

          columns: columnList,
          rows: users,
          addRow: addUser,
          addRows: addUsers,
          isLoading: loadingValue,
          changeLoadingState,

          initialPagination: Vue.ref({
            rowsPerPage: 30
          }),

          showDetail,

          toggleFullScreen,
        }
      },

    })

    Quasar.Lang.set(Quasar.Lang.ja)

    app.use(Quasar, {
      config: {
        lang: 'ja',
        dark: 'auto',
      },
    })

    return app.mount('#kintone-vue3-app')
  }

  kintone.events.on(events.index, async (e) => {
    if ( e.viewType!=='custom' || e.viewId!==targetViewId ) {
      return e;
    };

    vObj = generateView(e.appId)
    await getRecords(e.appId).then((resp) => {
      vObj.addRows(resp)
    })
    vObj.changeLoadingState(false);

    return e;
  });
})(Vue,Quasar);

4. 説明

4-1. スクリプトを動作させる一覧画面を指定する

part of customizeView.js
    if (event.viewType!=='custom' || event.viewId!==5536090) {return event;};

アプリ内のいろんなページで動作してもらっては困るので、しょっぱなで一覧IDを指定しておきます。
この箇所にあるevent.viewIdは環境ごとにかわりますので、5536090の箇所を、前段階で作成した一覧画面に記載の一覧IDに書き換えてください!

4-2. Vueオブジェクト生成時のポイント

setup() {}内に設定しているレコード一覧users等をref()でラップして設定

part of customizeView.js
    const app = Vue.createApp({
      setup (props, context) {
        const $q = Quasar.useQuasar()

        const columnList = [
          { name: 'userName', align: 'center', label: 'ユーザー名', field: row => row.userName.value, sortable: true },
          { name: 'userID', align: 'center', label: 'ユーザーID', field: row => row.userID.value, sortable: true },
          { name: 'mailAddress', align: 'center', label: 'メールアドレス', field: row => row.mailAddress.value, sortable: true },
          { name: 'birthday', align: 'center', label: '生年月日', field: row => row.birthday.value, sortable: true },
          { name: 'hogehoge', align: 'center', label: 'ふがふが', field: row => row.hogehoge, sortable: true },
        ]

        const users = Vue.ref([]);
        function addUser(value) {
          users.value.push(value)
        }
        function addUsers(value) {
          users.value = value
        }

        const loadingValue = Vue.ref(true);
        function changeLoadingState(value) {
          loadingValue.value = value
        }

        function showDetail(item) {
          const url = `/k/${appId}/show#record=${item.$id.value}`
          window.open(url, '_blank')
        }

        function toggleFullScreen (e) {
          const target = e.target.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
  
          $q.fullscreen.toggle(target)
          .then(() => {
            console.info('fullscreen button push')
          })
          .catch((err) => {
            alert(err)
          })
        }

        Vue.onMounted(() => {
          console.info($q)
          console.info($q.platform)
        })

        return {
          tableTitle: '利用者一覧',

          columns: columnList,
          rows: users,
          addRow: addUser,
          addRows: addUsers,
          isLoading: loadingValue,
          changeLoadingState,

          initialPagination: Vue.ref({
            rowsPerPage: 30
          }),

          showDetail,

          toggleFullScreen,
        }
      },

    })

<q-data-table>のデータ本体としたいrowsは初期段階で空配列としています。
対象データが大量な場合は表示までにレスポンスがなく、ユーザビリティが低下するのを防ぐ狙いがあります。
BooleanのisLoadingを用意してロード中という事を伝えているのも同じ理由です。

なお
 rowsはHTML内の:items="rows"と、
 isLoadingはHTML内の:loading="isLoading"と、
それぞれ関連付けています。


vue3ではcomposition APIという仕組みが使えるようになっていて、

part of customizeView.js
        const loadingValue = Vue.ref(true);
        function changeLoadingState(value) {
          loadingValue.value = value
        }

といった、"値"と、"それに対して行いたい処理" をまとめた記述ができというのが大きなメリットになっています。
vue2ではmethods内やcomputed内に分散させなければならなかったので助かります。
現在はパッと見で全容を把握・整理できる分量の開発しかしていないのでメリットを感じづらいですが、大規模な開発になるにつれこのメリットが活きてくるのではと思います。

タイトル行を設定

part of customizeView.js
        const columnList = [
          {
            name: 'userName', //キー項目
            align: 'center', //セル内での横方向表示位置
            label: 'ユーザー名', //表示名
            field: row => row.userName.value, //kintoneレコードのどの値を表示するか
            sortable: true, //タイトル行クリックでの並べ替えを使うか
          },
          { name: 'userID', align: 'center', label: 'ユーザーID', field: row => row.userID.value, sortable: true },
          { name: 'mailAddress', align: 'center', label: 'メールアドレス', field: row => row.mailAddress.value, sortable: true },
          { name: 'birthday', align: 'center', label: '生年月日', field: row => row.birthday.value, sortable: true },
          { name: 'hogehoge', align: 'center', label: 'ふがふが', field: row => row.hogehoge, sortable: true },
        ]

        // ~
        return {
          columns: columnList,
        }

columnsを用意し、<q-data-table>のタイトル行として設定します。
HTML内の:columns="columns"でこのオブジェクトと関連付けています。

keyに記述する内容は、kintoneのフィールドコード+「.value」といつも使っている形式で変更なし、getRecords()した内容をそのまま使うわけではない場合は、最後の「ふがふが」列のようになります。

フルスクリーンできるボタンを設置

part of customizeView.js
        const $q = Quasar.useQuasar()
        // ~
        function toggleFullScreen (e) {
          const target = e.target.parentNode.parentNode
  
          $q.fullscreen.toggle(target)
          .then(() => {
            console.info('fullscreen button push')
          })
          .catch((err) => {
            alert(err)
          })
        }
        return {
          toggleFullScreen,
        }

サンプルページにフルスクリーンの設定があったので追加しました。

肝心のQuasarを、プラグインとして使うよう設定

part of customizeView.js
    Quasar.Lang.set(Quasar.Lang.ja)

    app.use(Quasar, {
      config: {
        lang: 'ja',
        dark: 'auto',
      },
    })

QuasarをVue3のプラグインとして設定します。

  • 日本語利用
  • ダークモードの利用はautoを設定することで、ブラウザの状態に合わせて切り替えてくれるようになります。
    固定で通常のモードにしたい場合はdark: false,を、
    ダークモードにしたい場合はdark: true,にしても大丈夫です。

マウント

part of customizeView.js
    return app.mount('#kintone-vue3-app')

ここはVuetify3を使う場合と同じです。

↑ここまでの処理が一通り終わったら…

part of customizeView.js
    await getRecords(e.appId).then((resp) => {
      vObj.addRows(resp)
    })
    vObj.changeLoadingState(false);

最後にレコード取得&設定と、ローディングバー非表示して完了です。
それぞれsetup()内で作成した関数addRows()changeLoadingState()を介して、リアクティブなオブジェクトの値を更新しています。

5. アプリにライブラリ等を反映

アプリ管理 → 設定 → カスタマイズ/サービス連携 → JavaScript/CSSでカスタマイズ
の中に、上から順に以下内容を追加します。

全て指定できたら「保存」→「アプリを更新」します。

6. 完成

ここまでのステップが終了すると、こんな感じになります↓
完成

7. さいごに

アプリタイトルの文字が大きくなりすぎているのが難点ですが、全体的にはVuetify3よりかはkintoneに優しい(笑)ライブラリかなと思いました。
Vuetifyよりも日本語の解説サイトが少なく勉強に難儀していますが、これをスタートラインに色々作成していきたいです!

Discussion