🎄こちらは kintone2 Advent Calendar 2020 4日目の記事です🎅
はじめに
最近よく聞くようになったJavaScriptフレームワーク **Svelte(スヴェルト)**を使って
kintoneカスタマイズをしてみました。
Vue.jsを知っていればなんの問題もなく扱えると思います。そしてVueより手軽!
Svelte とは
Svelteは、Rich Harrisによって作られたJavaScript framework(英語版)の1種である。
Svelteのアプリケーションにはフレームワークのコードへの参照が含まれない。
その代わりに、Svelteのアプリケーションの構築時にDOMを操作するコードを生成する。
これによって、クライアント側での実行時性能が改善される場合がある。
▲ Wikipedia より
うーん、イマイチよくわからないですねw
簡単に言うと、
-
.svelte
という独自の拡張子ファイルで作成 - コード量が少なくすむ(らしい)
- 変更箇所のみレンダリングするけど仮想DOMは使わない
って感じですかね。あまり説明になっていない気もしますが、
便利らしいので四の五の言わずとりあえずコード書いてみましょう 笑
▼ 仮想DOM
Reactなどで使われている方法。ページ変更前と変更後の仮想DOMを比較して、その差分のみ描画する。
→ 毎回全部を描画しなくて済むので描画を素早くできる
一方で、仮想DOMを挟むことで回りくどい書き方になったりしてコストがかかったりもする(オーバーヘッド)
今回作ったもの
ReactやVueでも同じもの作って比較できたらいいなーと思い、kintoneのレコード一覧画面 を擬似的に作ってみました。
ちなみにサンプルデータは2020年のBBの社外活動履歴。出張がほとんどできなかった。つらい。
社内でもいろいろとやっているので暇ってことはなかったなぁ。むしろ過去一番忙しかった年かも 笑
デモ
GitHub Pagesで公開してみました。
https://rybb.github.io/svelte-kintone/
この記事では説明していないのですが、ソート機能もしれっとついてます^^
サンプルコード:https://github.com/RyBB/svelte-kintone/blob/main/src/App.svelte
作り方
さくっと流れを説明します。これからSvelte使ってみたいって方でもわかるようになるべく丁寧に説明してみます。
準備
- Node.js
- npm/npx コマンドが使える状態
- VSCode
- 迷ったらこれ。他にこだわりがなければこれ。
- Svelte用のVSCode拡張
- (kintone-customize-uploader)
- あると便利。
プロジェクトの作成
コマンド叩くだけです。VSCode上のTerminal使うと楽です。
とりあえずプロジェクト名は svelte-kinview にします(ダサい)
$ npx degit sveltejs/template svelte-kinview
npxはnpmにバンドルされたちょっと便利なコマンド。Node入れたら勝手に入るはず。
本来だと、
- ツールのインストール
- インストールしたツールの実行
- (もう使わない場合は)ツールの削除
と2~3段階踏まないといけないのですが、npxを使うとこの1~3までを一発でできます。便利!
ツールのインストール
上記のコマンドで、svelteの雛形が出来上がるので、cd
して npm install
で必要なツールをインストールします。
$ cd svelte-kinview
$ npm install
これでとりあえずSvelteを使って開発する環境は整いました。
ls
で確認すると、
$ ls
# こんな感じになっていればOK
# README.md package.json package-lock.json node_modules/ public/ rollup.config.js scripts/ src/
treeコマンドで見るとこんな感じ。
├── README.md
├── package.json
├── package-lock.json
├── node_modules
│ └── (省略)
├── public
│ ├── favicon.png
│ ├── global.css
│ └── index.html
├── rollup.config.js
├── scripts
│ └── setupTypeScript.js
└── src
├── App.svelte
└── main.js
生成されたファイルのざっくり紹介
とりあえず特徴的なやつだけピックアップ。
-
public/
- あまり気にしなくて良いですが、最終的にはこのディレクトリ内にJS/CSSが出来上がります
-
rollup.config.js
- 気にしなくて良いです
- (rollupはWebpack的な立ち位置のものです。
.svelte
を.js
にするための設定ファイル)
-
scripts/
- 気にしなくて良いです
-
src/
- 気にします。むしろこれだけ気にすればOK!
- メインファイルたちが入っています
main.js
メインとなる.svelte
ファイルを読み込むJSファイル。DOMのこの部分をいじるよってのが明記されている。
おそらく最初の1回だけいじってあとはあまり触らないかな。
App.svelte
Svelteのメインファイルです。ここにSvelte方式でコードを記述します。
Vueのように html/js/css が1ファイルに書けます。
コード修正
出来上がったJSファイルはkintoneへアップロードする前提なのでkintone JavaScript APIを使っていきます。
main.js
真ん中部分をkintone用に書き換えます。
import App from './App.svelte';
kintone.events.on('app.record.index.show', event => {
const app = new App({
target: document.getElementById('app'),
});
});
export default app;
あとでkintone側を設定するときにカスタマイズビューに <div id="app"></div>
と記載するので、
その部分に埋め込むよって感じの意味ですね。
App.svelte
html/js/css を1ファイルにまとめて書きます。
※ 残念ながらQiitaのMarkdownにはSvelte用の拡張はなかった。。
<script>
JSをここに書く
</script>
<main>
HTMLをここに書く
</main>
<style>
CSSをここに書く
</style>
こんな構成です。シンプルでわかりやすいですね。
※ 順番は決まっていないようで、html/js/cssって配置しても問題なかったです。
js部分
- kintoneの特定のアプリからレコードデータを取得
- そのレコードデータを自作一覧に描画
という流れにしました。なのでJSでは 特定アプリからレコードを取得して配列を返す って処理になっています。
<script>
// methods
async function getRecords() {
const app = XXX;
const query = 'order by $id desc limit 500';
const data = await kintone.api(kintone.api.url('/k/v1/records'), 'GET', {app, query});
return data.records;
}
</script>
JS側は終わり。これだけ。
html部分
レコードが取得できたら、その取得したデータを使ってテーブルを描画をします。
ここでキモとなるのが レコードが取得できたら です。つまりJS側の処理を待ってあげる必要があります。
通常、HTML側に記載した内容は問答無用で画面読み込み時に描画されるので、
JSの処理を待ってから画面描画をやりたい場合は JS側でHTMLの描画の処理も書かないといけない です。
→ jQueryとかはそんな感じですね。ただ分業を考えるとあまりよろしくない気がします。
ただ Svelte にはAwait blocks
というのがあり、これをHTML側に書くことで、
JS側の関数などの処理を待ってからHTMLの描画をさせることができます。
<main>
{$await getRecords() then records}
<table>
<tr></tr>
...
</table>
{/await}
</main>
あとから {#await} {/await}
を挿入することも容易なので分業しやすいですね。
で、その後は取得したデータ(配列)の数分だけ<tr></tr>
を作っていきたいわけですが、これもSvelteの Each blocks
を使えば
{#each records as record}
<tr class="origin-tr recordlist-row-gaia">
<td class="origin-td recordlist-cell-gaia">{record.レコード番号.value}</td>
<td class="origin-td recordlist-cell-gaia">{record.日付.value}</td>
<td class="origin-td recordlist-cell-gaia">{record.イベント名.value}</td>
<td class="origin-td recordlist-cell-gaia">{record.都道府県.value}</td>
</tr>
{/each}
と書けます。取得したデータ数が増えれば自動でこのループも増えてテーブルの行が増えるって感じです。
それが1行挿入するだけでできるのでかなり楽ですね。
ということで、html部分の全体はこんな感じに
<main>
{#await getRecords() then records}
<table class="origin-table recordlist-gaia">
<thead>
<th class="origin-th recordlist-header-cell-gaia">レコード番号</th>
<th class="origin-th recordlist-header-cell-gaia">日付</th>
<th class="origin-th recordlist-header-cell-gaia">イベント名</th>
<th class="origin-th recordlist-header-cell-gaia">都道府県</th>
</thead>
<tbody>
{#each records as record}
<tr class="origin-tr recordlist-row-gaia">
<td class="origin-td recordlist-cell-gaia">{record.レコード番号.value}</td>
<td class="origin-td recordlist-cell-gaia">{record.日付.value}</td>
<td class="origin-td recordlist-cell-gaia">{record.イベント名.value}</td>
<td class="origin-td recordlist-cell-gaia">{record.都道府県.value}</td>
</tr>
{/each}
</tbody>
</table>
{/await}
</main>
ちなみに、awaitの位置をeachの直前に持ってくると、
「テーブルのヘッダーだけ先に描画して、レコード取得後に中身の行も表示」って動きになります。お好みで!
<main>
<table class="origin-table recordlist-gaia">
<thead>
<th class="origin-th recordlist-header-cell-gaia">レコード番号</th>
<th class="origin-th recordlist-header-cell-gaia">日付</th>
<th class="origin-th recordlist-header-cell-gaia">イベント名</th>
<th class="origin-th recordlist-header-cell-gaia">都道府県</th>
</thead>
<tbody>
{#await getRecords() then records}
{#each records as record}
<tr class="origin-tr recordlist-row-gaia">
<td class="origin-td recordlist-cell-gaia">{record.レコード番号.value}</td>
<td class="origin-td recordlist-cell-gaia">{record.日付.value}</td>
<td class="origin-td recordlist-cell-gaia">{record.イベント名.value}</td>
<td class="origin-td recordlist-cell-gaia">{record.都道府県.value}</td>
</tr>
{/each}
{/await}
</tbody>
</table>
</main>
css部分
kintoneのクラスだけでは見た目が整わなかったので少しだけ自分で設定しました。
(そもそもkintoneの一覧とはHTMLの構成が異なるので仕方ない)
<style>
.origin-th,
.origin-td {
padding: 5px 10px;
}
.origin-th {
height: 45px;
}
.origin-td {
height: 40px;
vertical-align: middle;
}
</style>
App.svelte全体
これをぺっと貼ればOK!
<script>
// methods
async function getRecords() {
const app = XXX;
const query = 'order by $id desc limit 500';
const data = await kintone.api(kintone.api.url('/k/v1/records'), 'GET', {app, query});
data.records.forEach(val => {
val['レコード番号'].value = Number(val['レコード番号'].value);
if (!val['都道府県'].value) val['都道府県'].value = 'オンライン';
});
return data.records;
}
</script>
<main>
{#await getRecords() then records}
<table class="origin-table recordlist-gaia">
<thead>
<th class="origin-th recordlist-header-cell-gaia">レコード番号</th>
<th class="origin-th recordlist-header-cell-gaia">日付</th>
<th class="origin-th recordlist-header-cell-gaia">イベント名</th>
<th class="origin-th recordlist-header-cell-gaia">都道府県</th>
</thead>
<tbody>
{#each records as record}
<tr class="origin-tr recordlist-row-gaia">
<td class="origin-td recordlist-cell-gaia">{record.レコード番号.value}</td>
<td class="origin-td recordlist-cell-gaia">{record.日付.value}</td>
<td class="origin-td recordlist-cell-gaia">{record.イベント名.value}</td>
<td class="origin-td recordlist-cell-gaia">{record.都道府県.value}</td>
</tr>
{/each}
</tbody>
</table>
{/await}
</main>
<style>
.origin-th,
.origin-td {
padding: 5px 10px;
}
.origin-th {
height: 45px;
}
.origin-td {
height: 40px;
vertical-align: middle;
}
</style>
Svelteのコンパイル&適用
main.js と App.svelte の修正ができたら、これらのファイルをコンパイルしてJS/CSSファイルに変換します。
といっても難しい処理は特になく npm run build
とコマンドを実行するだけ。
$ npm run build
public/build/
に bundle.js
やbundle.css
ができるのでこれをkintoneにアップロード!
kintone側の設定
データのマスタとなるアプリは適当に作ってください 笑
このSvelteで作った一覧を埋め込むアプリにはカスタマイズビューで <div id="app"></div>
とだけ書けばOKです。
(本当は一覧viewIdが違ったら〜って処理も書くべきですが割愛w)
発展
今回のサンプルだとkintoneのフィールドはベタ書きしていて、列が増えるとHTML側の記述も増えて大変なのですが、
あらかじめJS上で配列にまとめておくと、ここでも Each blocks
が使えるのでおすすめです。
<script>
// ~省略~
const fields = ['レコード番号', '日付', 'イベント名', '都道府県'];
</script>
<main>
{#await getRecords() then records}
<table class="origin-table recordlist-gaia">
<thead>
{#each fields as field}
<th class="origin-th recordlist-header-cell-gaia">{field}</th>
{/each}
</thead>
<tbody>
{#each records as record}
<tr class="origin-tr recordlist-row-gaia">
{#each fields as field}
<td class="origin-td recordlist-cell-gaia">{record[field].value}</td>
{/each}
</tr>
{/each}
</tbody>
</table>
{/await}
</main>
おわりに
Vueに慣れているってものありますが、それでもかなり楽に実装できた気がします。
Vueと同じくHTML側で指定するDOMは1つだけなので、ポータルカスタマイズとの相性も良さそうですね〜
さくっと見た目を整えたいときとかに便利なので今後も使っていきたいと思います!
それでは!≧(+・` ཀ・´)≦