kintone(キントーン)は、業務などで手軽に使えるWebデータベースアプリを提供しています。
簡易的な操作は標準で提供されているビューワーアプリで十分ですが、
デバイスの機能を使ったり、こだわったインターフェースを使いたい時には、
独自のアプリを開発することになります。
今回はkintoneアプリの一つ、「院内掲示板アプリ」のデータをMonacaアプリとして呼び出し、操作する手順を解説します。
前編と後編の2つの記事に分けて解説します。
前編であるこの記事では、次の機能を作成していきます。
- アプリへのログイン
- 掲示板スレッドの一覧表示
- 掲示板へのデータ登録
また、アプリ開発の中では、いくつかの注意点もあるので、合わせて解説します。
また、プロジェクトのソースコードは、GitHubに配置しています。
https://github.com/monaca-samples/kintone-bbs
アプリの仕様について
今回のアプリで利用するフレームワークは、Framework7を利用しています。
ルーティングファイル(routes.js)にて、画面を次の通りに設定しています。
最初に読み込まれるのは、ログインフォームを表示するHome画面です。
// ファイル: js/routes.js
const routes = [
{
path: "/",
url: "./index.html",
},
// ログイン
{
path: "/home",
name: "Home",
componentUrl: "./pages/home.html"
},
// スレッド一覧画面
{
path: "/threads",
name: "Threads",
componentUrl: "./pages/threads.html"
},
// スレッド詳細画面
{
path: "/threads/:id",
name: "Thread",
componentUrl: "./pages/thread.html"
},
// スレッド新規作成画面
{
path: "/form",
name: "Form",
componentUrl: "./pages/form.html"
},
// Default route (404 page). MUST BE THE LAST
{
path: "(.*)",
url: "./pages/404.html",
},
];
index.htmlでは 、Framework7のViewとライブラリdayjsを読み込んでいます。
// ファイル: www/index.html
<div id="app">
<!-- Views/Tabs container -->
<div class="views tabs safe-areas">
<!-- Your main view/tab, should have "view-main" class. It also has "tab-active" class -->
<div id="view-home" class="view view-main">
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.10.7/dayjs.min.js" integrity="sha512-bwD3VD/j6ypSSnyjuaURidZksoVx3L1RPvTkleC48SbHCZsemT3VKMD39KknPnH728LLXVMTisESIBOAb5/W0Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
kintoneの変数定義
以下の3つの変数が、kintone操作用の変数です。
これは js/app.js の中で定義しています。
KINTONE_DOMAIN、KINTONE_APP_IDはそれぞれ自身のものに書き換えてください。
AUTHは認証用として用いるので、ログイン時に内容を設定する変数になります。
// ファイル: js/app.js
const KINTONE_DOMAIN = "xxxx.cybozu.com";
const KINTONE_APP_ID = "1";
let AUTH; // 認証用
ログイン
ログインは pages/home.html
に実装しています。
kintoneでは、最初にID/パスワードによるログインを行います。
この時、認証だけを行うAPIはないため、ログイン情報を使って任意のAPI(今回はアプリ情報を取得するAPI)を実行します。
その結果が返ってくるかどうかで、認証の成否を判定しています。
認証がうまくいったら、一覧の画面に遷移しています。
// ファイル: www/pages/home.html
// ログインボタンを押した時の処理
const login = async () => {
// 入力値を受け取る
const username = $f7.$el.find('#username').val();
const password = $f7.$el.find('#password').val();
// APIリクエスト用のヘッダー文字列作成
AUTH = ${username}:${password}
;
try {
// APIリクエスト
const res = await getApp({id: KINTONE_APP_ID});
// レスポンスで認証情報の正しさを判定
const text = res.data.appId === KINTONE_APP_ID ? 'ログインしました' : res.message;
// トースト表示
$f7.toast.create({
text,
position: 'top',
closeTimeout: 500,
}).open();
// 一覧画面に遷移
$f7router.navigate({
name: 'Threads',
});
} catch (e) {
console.log(e.message);
}
};
getApp
関数は js/app.js
に定義しています。
その他、アプリ内で共通で利用する関数として、sendRequest
や getHeaders
、 promisify
を用意しています。
// ファイル: js/app.js
// アプリ情報取得関数
const getApp = ({ id }) => {
const options = {
method: 'get',
params: { id },
};
return sendRequest(/app.json
, options);
}
// リクエスト送信用共通関数
const sendRequest = (path, options, type = 'json', responseType = 'json') => {
cordova.plugin.http.setDataSerializer(type);
options.headers = getHeaders();
options.responseType = responseType;
return promisify(cordova.plugin.http.sendRequest)(
https://${KINTONE_DOMAIN}/k/v1${path}
, options
);
}
// kintoneリクエスト用の共通ヘッダー作成関数
const getHeaders = () => {
return {
'X-Cybozu-Authorization': btoa(unescape(encodeURIComponent(AUTH)))
}
}
// コールバック方式をPromise形式に変換する関数
function promisify(cdvFunc) {
return function() {
return new Promise((res, rej) => {
cdvFunc(...arguments, res, rej);
});
}
}
スレッド一覧の取得
掲示板スレッドの一覧を表示する画面は、 pages/threads.html
に記述しています。
kintoneからスレッド一覧を取得するには、レコードの取得(GET) – cybozu developer networkを利用します。
// ファイル: www/pages/threads.html
let records = []; // スレッド一覧用
// 画面が表示されたら呼び出されるイベント
$on("pageAfterIn", async (e, page) => {
// スレッド一覧の取得
const res = await getRecords({
app: KINTONE_APP_ID,
});
// 結果を変数に入れる
records = res.data.records;
// 画面を更新
$update();
});
getRecords
関数は js/app.js
に記述しています。
// ファイル: js/app.js
// レコード情報取得関数
const getRecords = (params) => {
const options = {
method: 'get',
params,
};
return sendRequest(/records.json
, options);
}
データを取得したら、 $update()
で描画を更新しています。
描画するHTML側では、スレッド一覧をtableタグを利用して表示しています。
// ファイル: www/pages/threads.html
<table>
<thead>
<tr>
<th>#</th>
<th>配信日</th>
<th>タイトル</th>
<th>重要度</th>
</tr>
</thead>
<tbody>
${records.map(r => $h`
<tr @click=${() => click(r)}>
<td>${r.$id.value}</td>
<td>${r.pubDate.value}</td>
<td>${r.title.value}</td>
<td>${r.level.value}</td>
</tr>
`)}
</tbody>
</table>
行をタップした際には、 click
関数を呼び出しています。
// ファイル: www/pages/threads.html
// スレッド一覧をタップした際のイベント
const click = (record) => {
// スレッド詳細画面に遷移
$f7router.navigate(/threads/${record.$id.value}
, {
props: {
record,
},
});
}
データの新規作成
データの新規作成は、 /form
へ画面遷移します。
これは pages/threads.html
のヘッダーにある「+」アイコンをタップした際のリンク先です。
// ファイル: pages/threads.html
<div class="right">
<a href="/form" class="link">
<i class="f7-icons">plus</i>
</a>
</div>
フォームについて
pages/form.html
の内容です。
ここではフォームを表示して入力を行います。
掲示板のタイトルやスレッド内容のテキストや日付、ファイルを指定できます。
// ファイル: www/pages/form.html
<form id="report">
<div class="list no-hairlines-md">
<ul>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-input-wrap">
<input type="text" name="title" placeholder="掲示板のタイトル" value="掲示板のタイトル" />
<span class="input-clear-button"></span>
</div>
</div>
</li>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-input-wrap">
<input type="date" name="pubDate" placeholder="2022-02-10" value="2022-02-10" />
</div>
</div>
</li>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-input-wrap">
<select name="level">
<option value="通常" selected>通常</option>
<option value="至急">至急</option>
<option value="重要">重要</option>
</select>
</div>
</div>
</li>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-input-wrap">
<textarea name="body" class="resizable"></textarea>
</div>
</div>
</li>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-input-wrap">
<input type="file" name="attachment" multiple />
</div>
</div>
</li>
<li>
<a href="#" @click=${save} class="item-link list-button">保存する</a>
</li>
</ul>
</div>
</form>
そして、保存ボタンを押すと save
関数が呼ばれます。
// ファイル: www/pages/form.html
// 新しいスレッドを保存するイベント
const save = async (e) => {
// フォームデータをkintoneフォーマットに整形(加えてファイルのアップロード)
const params = await formToObject($(e.target).parents("form")[0]);
try {
// リクエストデータの作成
const data = {
app: KINTONE_APP_ID,
record: params
}
// レコードの登録
const res = await addRecord(data);
// データが登録できればメッセージ表示
const text = res.data.revision ? "保存しました" : res.message;
$f7.toast.create({
text,
position: "top",
closeTimeout: 2000,
}).open();
$f7router.back();
} catch (e) {
console.log(e);
}
};
save
関数には、入力データをkintoneのデータフォーマットに整形している formToObject
関数があります。
この関数では、ファイルのアップロードも行い、fileKeyというデータを取得しています。
// ファイル: www/pages/form.html
// フォームのデータをオブジェクトに変換する関数
const formToObject = async (form) => {
const obj = {};
const ary = Array.prototype.slice.call(form.elements);
for (const ele of ary) {
const { name, type, value } = ele;
if (type !== "file") {
obj[name] = { value };
continue;
}
// ファイルのアップロード
const promises = [];
for (const file of ele.files) {
promises.push(uploadFile(file));
}
const responses = await Promise.all(promises);
// アップロードしたファイルをkintoneのデータフォーマットに整形
obj[name] = {
value: responses.map(response => {
return {
fileKey: response.data.fileKey
}
}
)};
}
return obj;
}
uploadFile
関数は js/app.js
に定義しています。
// ファイル: js/app.js
// ファイルアップロード用関数
const uploadFile = (file) => {
const data = new FormData();
data.append('file', file, file.name);
const options = {
method: 'post',
data,
};
return sendRequest(/file.json
, options, 'multipart');
}
そして、データをオブジェクトにしたら、 addRecord
関数を使ってレコード登録しています。
この関数も js/app.js
に定義しています。
// ファイル: js/app.js
// レコード登録用関数
const addRecord = (data) => {
const options = {
method: 'post',
data,
};
return sendRequest(/record.json
, options);
}
注意点
Cordovaプラグインを利用します
kintoneでは、CORS制限によってMonacaアプリからデフォルトのXMLHTTPオブジェクトを使った通信はできません。
代替として、プラグインのcordova-plugin-advanced-httpを利用します。
これは、ネイティブのネットワーク機能を使うCordovaプラグインになります。
インタフェースは専用のオブジェクトになっていて、XMLHTTPRequestとは互換性がないので注意してください。
kintone側のフィールド設定を更新
kintoneでは標準で日本語のフィールド名になっています。
このままだとJavaScriptからアクセスしづらいため、英語名でフィールドを更新します。
今回は次のように変更しています。
フィールド名 | フィールドコード |
---|---|
タイトル | title |
重要度 | level |
配信内容 | body |
配信日 | pubDate |
関連資料 | attachment |
まとめ
kintoneアプリのデータを取得したり、登録したりするのは多少の癖がありますが、慣れれば難しくないでしょう。kintone公式のJavaScript SDKではCORSの制限があり利用できないので注意が必要です。
Monacaでアプリ開発をすることで、kintoneを拡張して外部システムと連携したり、スマホやタブレットで利用したりとkintone活用の幅を広げることができます。