kintoneライクなスタイルシートを利用してプラグインの設定画面を作ってきたけれど,テーブルなどを実装するのが辛いと感じている方は多いのではないでしょうか. React + kintone UI Componentを使えば,複雑なUIも簡単に実装できます.
通常のJSカスタマイズでReactを使いたい方は,こちらの記事などが参考になるかと思います.
開発準備
React + kintone UI Componentで書いたコードを,自動で「コンパイル→プラグインzip化→kintoneにアップロード」する開発環境を整えます.
@kintone/create-pluginのインストール
グローバルに,@kintone/create-pluginをインストールします. インストール済みの場合はスキップしてください.
npm i -g @kintone/create-plugin
プラグイン開発ディレクトリの作成
@kintone/create-pluginのコマンドで,プラグイン開発ディレクトリを作成します.
create-kintone-plugin sample
kintoneプラグインのプロジェクトを作成するために、いくつかの質問に答えてください :)
では、はじめましょう!
? プラグインの英語名を入力してください [1-64文字] sample
? プラグインの説明を入力してください [1-200文字] sample
? 日本語をサポートしますか? Yes
? プラグインの日本語名を入力してください [1-64文字] (省略可)
? プラグインの日本語の説明を入力してください [1-200文字] (省略可)
? 中国語をサポートしますか? No
? プラグインの英語のWebサイトURLを入力してください (省略可)
? プラグインの日本語のWebサイトURLを入力してください (省略可)
? モバイルページをサポートしますか? No
? @kintone/plugin-uploaderを使いますか? Yes
プラグイン開発ルートディレクトリへの移動
作成したディレクトリに移動します.
cd sample
依存パッケージのインストール
依存パッケージをインストールします.
npm i -D webpack webpack-cli babel-loader @babel/core @babel/preset-env @babel/preset-react css-loader style-loader @kintone/webpack-plugin-kintone-plugin
npm i react react-dom @kintone/kintone-ui-component
プラグイン開発ディレクトリ(sample/)内のファイルの書き換え
プラグイン開発ディレクトリ(sample/)内のファイルを書き換えていきます.
「/package.json」の書き換え(scriptという箇所)
・/package.json
{
...
"scripts": {
"start": "node scripts/npm-start.js",
"upload": "kintone-plugin-uploader dist/plugin.zip --watch --waiting-dialog-ms 3000",
"develop": "webpack --mode development --watch",
"build": "webpack --mode production",
"lint": "eslint src/*/*"
},
...
}
「/.eslintrc.js」の書き換え
・/.eslintrc.js
'use strict';
module.exports = {
extends: [
'@cybozu',
'@cybozu/eslint-config/globals/kintone',
'@cybozu/eslint-config/presets/react'
]
};
「/webpack.config.js」の新規作成
・/webpack.config.js
const path = require('path');
const KintonePlugin = require('@kintone/webpack-plugin-kintone-plugin');
module.exports = {
entry: {
desktop: './src/desktop/index.jsx',
config: './src/config/index.jsx'
},
output: {
path: path.resolve(__dirname, 'plugin'),
filename: '[name].js'
},
module: {
rules: [{
test: /(\.js|\.jsx)$/,
exclude: /node_modules/,
use: [{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/react']
}
}]
}, {
test: /\.css$/,
use: [{loader: 'style-loader'}, {loader: 'css-loader'}]
}]
},
resolve: {
extensions: ['.js', '.jsx']
},
plugins: [
new KintonePlugin({
manifestJSONPath: './plugin/manifest.json',
privateKeyPath: './private.ppk',
pluginZipPath: './dist/plugin.zip'
})
]
};
「/plugin」ディレクトリの新規作成
「/plugin」ディレクトリを新規作成します.
「/plugin/manifest.json」の新規作成
・/plugin/manifest.json
{
"manifest_version": 1,
"version": 1,
"type": "APP",
"desktop": {
"js": [
"desktop.js"
]
},
"icon": "icon.png",
"config": {
"html": "config.html",
"js": [
"config.js"
]
},
"name": {
"en": "sample"
},
"description": {
"en": "sample"
}
}
「/plugin/config.html」の新規作成
・/plugin/config.html
<div id="config-root"></div>
アイコンファイルの移動
アイコンファイルの「/src/image/icon.png」を「/plugin/icon.png」への移動します.
「/src」ディレクトリの中身の削除
残った「/src」ディレクトリの中身を削除します.
「/src/config」ディレクトリの新規作成
「/src/config」ディレクトリを新規作成します.
「/src/config/index.jsx」の新規作成
「/src/config/index.jsx」を新規作成します.
「/src/desktop」ディレクトリの新規作成
「/src/desktop」ディレクトリを新規作成します.
「/src/desktop/index.jsx」の新規作成
「/src/desktop/index.jsx」を新規作成します.
よく使うコマンド
npm run build
製品版「/dist/plugin.zip」を作成します.
npm run upload
kintoneに「/dist/plugin.zip」をアップロードします.
npm start
ファイルを監視し,自動で「コンパイル→プラグインzip化→kintoneにアップロード」します.
正常に動作しない場合は,一度「npm run build」を実行した後,「npm start」を実行してください.
サンプル
プラグイン設定画面にテーブルを実装します. 入力したデータを,一覧画面のメニュー部のドロップダウンの選択肢に利用します.
・/src/config/index.jsx
import React from 'react';
import {render} from 'react-dom';
import {Table, Text, Button} from '@kintone/kintone-ui-component';
(PLUGIN_ID => {
class App extends React.Component {
constructor(props) {
super(props);
this.defaultRowData = {text: ''};
this.state = {
data: props.data ? JSON.parse(props.data) : [this.defaultRowData]
};
}
render() {
return (
<div>
<Table
columns={[{
header: 'Text',
cell: ({rowIndex, onCellChange}) =>(
<Text
value={this.state.data[rowIndex].text}
onChange={value => onCellChange(value, this.state.data, rowIndex, 'text')}
/>
)
}]}
data={this.state.data}
defaultRowData={this.defaultRowData}
onRowAdd={({data}) => {
this.setState({data});
}}
onRowRemove={({data}) => {
this.setState({data});
}}
onCellChange={({data}) => {
this.setState({data});
}}
/>
<Button
text="submit"
onClick={() => {
kintone.plugin.app.setConfig({
data: JSON.stringify(this.state.data)
});
}}
/>
</div>
);
}
}
render(
<App data={kintone.plugin.app.getConfig(PLUGIN_ID).data} />,
document.getElementById('config-root')
);
})(kintone.$PLUGIN_ID);
・/src/desktop/index.jsx
import React from 'react';
import {render} from 'react-dom';
import {Dropdown} from '@kintone/kintone-ui-component';
(PLUGIN_ID => {
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [{
label: '-----',
value: ''
}].concat((props.data ? JSON.parse(props.data) : []).map(({text}) => ({
label: text,
value: text
}))),
value: ''
};
}
render() {
return (
<Dropdown
items={this.state.items}
value={this.state.value}
onChange={(value) => {
this.setState({value});
}}
/>
);
}
}
kintone.events.on(['app.record.index.show'], () => {
render(
<App data={kintone.plugin.app.getConfig(PLUGIN_ID).data} />,
kintone.app.getHeaderMenuSpaceElement()
);
});
})(kintone.$PLUGIN_ID);