経県値というアプリは知っていますか?各都道府県毎に、その地域で経験した内容を記録していくことで累計ポイントを獲得するアプリです。
今回はNCMBとMonacaを使って経県値風のアプリ(地図タップアプリ)を作ります。SVGを使って日本地図を表示し、経験したことをチェックしていきます。得点は出していませんが、ちょっとカスタマイズすればできるでしょう。
前回の記事では匿名認証と地図表示を実装しましたので、今回はデータの入力と検索を作成します。
コードについて
今回のコードはmemorymap-monacaにアップロードしてあります。実装時の参考にしてください。
都道府県をタップした際のイベント
各都道府県はSVGで描画されており、 pref(都道府県ID)
という形式で描画されています。以下はその該当部分 map.html
で、 attr
の処理がそれに当たります。
const view = svg.selectAll(".pref")
.data(topo)
.enter()
.append("path")
.attr("class", function(d) {
return "pref pref" + pref.indexOf(d.properties.name_local);
})
.attr("d", path);
この .pref
クラスのタップ時のイベントを取得します。
// 地図をタップした際のイベント
$('.pref').on('click', (e) => {
// タップした対象都道府県のID
prefId = $(e.target).attr('class').match(/pref([0-9]+)/)[1];
// 都道府県名
prefecture = pref[prefId];
// 表示更新
$update();
// アクションシートを開く
sheet.open();
});
シートモーダルについて
画面下などからシートを表示する機能はFramework7のものを使っています。まず、テンプレートになる部分をHTMLで作成します。
<div class="sheet-modal sheet-modal-push" style="height: auto">
<div class="sheet-modal my-sheet">
<div class="toolbar">
<div class="toolbar-inner">
<div class="title">${ prefecture }</div>
<div class="right"><a class="link sheet-close" href="#">保存</a></div>
</div>
</div>
<div class="sheet-modal-inner list">
<form id="status">
<input type="hidden" name="prefId" value="${ prefId }" />
<ul>
${ status_values.map(value => $h`
<li>
<label class="item-radio item-content">
<input type="radio" name="status" value="${ value }" />
<i class="icon icon-radio"></i>
<div class="item-inner">
<div class="item-title">${ value }</div>
</div>
</label>
</li>
`)}
</ul>
</form>
</div>
</div>
</div>
これをシートモーダルとして定義します。
const sheet = app.sheet.create({el: '.my-sheet'});
status_values
は経験したことのリストです。
const status_values = ['なし', '通過', '降りた', '歩いた', '泊まった', '住んだ'];
既存データをNCMBより取得する
すでに既存データがある場合に備えて、NCMBのデータストアからデータをダウンロードします。これは画面が表示された際のイベントで実行します。
fetch
関数は最初の1件だけを取得する関数です。今回はACLを使って自分のデータだけを表示・更新するよう制御しているので、この方法で問題ないでしょう。
// 既存データ用オブジェクト
let prefectureStatus = {};
// 画面が表示された際のイベント
const beforeIn = async (e) => {
// 既存データを取得
prefectureStatus = await PrefectureStatus.fetch();
// データがなければ新規作成
if (!prefectureStatus.objectId) prefectureStatus = new PrefectureStatus();
// 既存の入力情報を取得
const status = prefectureStatus.status || {};
// 都道府県の色を変更する
Object.keys(status).forEach(prefId => {
changeColor(prefId, status[prefId]);
});
};
$on('page:beforein', beforeIn); // 画面が表示される前に実行
$on('page:tabshow', beforeIn); // タブ切り替え時に実行
changeColor
関数はステータスの値に応じて背景色を返す関数です。
const changeColor = (prefId, status) => {
const dom = document.querySelector(`.pref${prefId}`);
dom.style.fill = getColor(status);
};
const getColor = (status) => {
switch (status) {
case 'なし': return '';
case '通過': return 'lightcyan';
case '降りた': return 'lightskyblue';
case '歩いた': return 'cornflowerblue';
case '泊まった': return 'blue';
case '住んだ': return 'navy';
}
}
そして取得したデータを使って、シートがオープンした際に既存入力値を取得します。
// シートを開いた際のイベント
sheet.on('open', (sheet) => {
// 既存データの取得
const values = prefectureStatus.status || {};
const status = values[prefId] || 'なし';
// ラジオボタンを選択状態にする
$(`form#status [name="status"][value="${status}"]`).prop('checked', true);
});
データを保存する
入力された後、シートを閉じるタイミングでデータを保存します。各都道府県ごとに1レコードではなく、同じレコードの中の status
カラムにオブジェクト形式でデータを保存します。データの取得件数を減らすことで、ネットワークアクセスを高速化できます。
// シートを閉じた際のイベント
sheet.on('close', async (sheet) => {
// 入力データの取得
const prefId = $('form#status [name="prefId"]').val();
const value = $('form#status [name="status"]:checked').val();
const params = { [prefId]: value };
// 都道府県の色を変更する
changeColor(prefId, value);
// 既存データとのマージ
const status = {...prefectureStatus.status, ...params};
// データをセット
prefectureStatus.set('status', status);
// アクセス権限の設定
const acl = new ncmb.Acl();
const user = ncmb.User.getCurrentUser();
acl.setUserReadAccess(user, true) // 自分だけ読み込み可能
.setUserWriteAccess(user, true); // 自分だけ書き込み可能
prefectureStatus.set('acl', acl);
// 新規保存または更新
if (prefectureStatus.objectId) {
await prefectureStatus.update();
} else {
await prefectureStatus.save();
}
});
データを一覧表示する
データの一覧表示は、地図と同じようにデータを取得するところからはじまります。
// ストアから都道府県一覧を取得する
const { prefectures } = $store.getters;
// ステータスを定義する
let status = {};
$on('page:tabshow', async (e) => {
// NCMBのデータストアを定義
const PrefectureStatus = ncmb.DataStore('PrefectureStatus');
// 自分のデータを取得する
const prefectureStatus = await PrefectureStatus.fetch();
// ステータスを取得する
status = prefectureStatus.status || {};
$update();
});
後は、この都道府県とステータスを使って一覧表示するだけです。
<div class="page-content">
<div class="data-table">
<table>
<thead>
<tr>
<th class="label-cell">都道府県</th>
<th class="label-cell">経験</th>
</tr>
</thead>
<tbody>
${ Object.keys(prefectures.value).map(prefId => $h`
<tr>
<td class="label-cell">${ prefectures.value[prefId] }</td>
<td class="label-cell">${ status[prefId] }</td>
</tr>
`)}
</tbody>
</table>
</div>
</div>
まとめ
これで地図タップアプリの完成です。地図に対して何らかのデータをセットするという使い方は良くあるかと思います。Googleマップなどの地図も良いですが、SVGを使ってみるのもカスタマイズしやすくて便利です。
データストアにはACL(アクセス権限)を正しく設定することで、自分だけのデータを安全・簡単に作成できます。ぜひ使いこなしてください。