NCMBとMonacaを使ってカレンダーアプリを作ります。予定を登録したり、Framework7のカレンダーコンポーネントを使って予定を表示できるというアプリです。
前回の記事では認証を実装しましたので、今回はカレンダーへのデータ登録と表示などを実装します。
コードについて
今回のコードはNCMBMania/monaca-calendar-appにアップロードしてあります。実装時の参考にしてください。
カレンダーの予定一覧について
ストアの利用
カレンダーの予定は、Framework7のストアを使って管理します。こうすることで、画面を遷移した後でもデータを一元管理でき、アクセスも行えます。
予定一覧と一緒に、選択した日付もストアに入れます。
// js/store.js に記述
const createStore = Framework7.createStore;
const store = createStore({
state: {
schedules: [], // スケジュール一覧
selectedDate: null, // 選択された日付
},
getters: {
schedules({ state }) {
return state.schedules;
},
selectedDate({ state }) {
return state.selectedDate;
},
},
actions: {
// スケジュール一覧をセット
addSchedules({ state }, schedules) {
state.schedules = schedules;
},
// 選択された日付をセット
setDate({ state }, date) {
state.selectedDate = date;
},
},
});
画面について
カレンダー画面は以下のようになります。カレンダーの表示自体はFramework7のコンポーネントを使うので #calendar
だけで済みます。
selectedDate.value
のように value
が付いているのはストアを利用しているからです。
<div id="calendar"></div>
<div class="block-title">
${ selectedDate.value ? moment(selectedDate.value).format('YYYY年MM月DD日') : "日付を選択してください"}
</div>
<div class="list media-list chevron-center">
${ selectedDate.value === null || schedules.value.length === 0 ?
$h`<div class="block">予定はありません</div>` :
$h`<ul>
${ schedules.value
.filter(schedule => moment(schedule.startDate.iso).format('YYYY-MM-DD') === moment(selectedDate.value).format('YYYY-MM-DD'))
.map(schedule => $h`
<li>
<a href="/form/${schedule.objectId}/" class="item-link item-content">
<div class="item-inner">
<div class="item-title-row">
<div class="item-title">${schedule.title}</div>
<div class="item-after">
${moment(schedule.startDate.iso).format('HH:mm')}〜${moment(schedule.endDate.iso).format('HH:mm')}
</div>
</div>
<div class="item-text">
${schedule.body}
</div>
</div>
</a>
</li>
`)}
</ul>`
}
</div>
予定の取得とストアへの設定
画面を表示したタイミングでNCMBのデータストアから予定一覧を取得しています。その結果は、ストアへセットします。
まずストアからデータを取得しておきます。
// ストアからデータを取得
const { schedules, selectedDate } = $store.getters;
そして画面が表示される際のイベントで、予定を取得する関数を実行します。
// 画面が表示される際に実行される処理
$on('page:beforein', async () => {
// NCMBから予定を取得
await getSchedule(new Date);
});
getSchedule
の内容は以下のようになります。デフォルトでは表示月の月初から月末までの予定をすべて取得し、それをストアに保存します。
// 選択された日付からデータストアを検索、予定の一覧を取得する処理
const getSchedule = async (date) => {
// データストアの準備
const Schedule = ncmb.DataStore('Schedule');
// データストアを検索
const results = await Schedule
.greaterThanOrEqualTo('startDate', moment(date).startOf('month').toDate()) // 開始日は入力値以上
.lessThan('endDate', moment(date).endOf('month').toDate()) // 終了日は明日未満
.limit(1000)
.fetchAll();
// 検索結果をストアに適用
$store.dispatch('addSchedules', results);
// 画面に表示
applySchedules();
};
カレンダー表示
カレンダーはFramework7のコンポーネントを使います。以下のコードを参照してください。
// 初期表示時の処理
$on('page:init', async () => {
// Framework7のカレンダーを初期化
calendar = $f7.calendar.create({
containerEl: '#calendar',
value: [new Date()],
events: [],
weekHeader: false,
renderToolbar: function () {
return `
<div class="toolbar calendar-custom-toolbar no-shadow">
<div class="toolbar-inner">
<div class="left">
<a href="#" class="link icon-only">
<i class="icon icon-back"></i>
</a>
</div>
<div class="center"></div>
<div class="right">
<a href="#" class="link icon-only">
<i class="icon icon-forward"></i>
</a>
</div>
</div>
</div>
`;
},
on: {
init: (c) => {
$('.calendar-custom-toolbar .center').text(`${c.currentYear}年${c.currentMonth + 1}月`);
$('.calendar-custom-toolbar .left .link').on('click', () => {
c.prevMonth();
});
$('.calendar-custom-toolbar .right .link').on('click', () => {
c.nextMonth();
});
},
// 年月を変更した際のイベント
monthYearChangeStart: async (c) => {
// 表示日付をリセット
$store.dispatch('setDate', null);
// 該当月のスケジュールを取得
await getSchedule(new Date(c.currentYear, c.currentMonth, 1));
// タイトルを修正
$('.calendar-custom-toolbar .center').text(`${c.currentYear}年${c.currentMonth + 1}月`);
},
// 日付を選択した際の処理
dayClick: (calendar, dayEl, year, month, day) => {
// 選択日付を設定
$store.dispatch('setDate', new Date(year, month, day));
}
}
});
});
カレンダーコンポーネントの events
に予定のデータを追加することで、カレンダー上にドット表示できるようになります。そのための関数が applySchedules
になります。
// 取得したスケジュールをカレンダーに反映する処理
const applySchedules = () => {
// Framework7用のイベント配列に変換
const events = schedules.value.map(schedule => ({
date: new Date(moment.parseZone(schedule.startDate.iso).format('YYYY/MM/DD')),
color: getColor(),
}));
// カレンダーに追加
calendar.params.events = events;
// 表示を更新
calendar.update();
}
getColor
関数はランダムな色を返すだけの関数です。
// ランダムな色を返す関数
const getColor = () => {
return `#${Math.floor(Math.random() * 16777215).toString(16)}`;
};
予定を追加・編集する
予定を追加したり、編集する際には form.html
へ移動します。これは calendar.html
のヘッダーメニューにあるプラスアイコンより遷移します。
<div class="navbar">
<div class="navbar-bg"></div>
<div class="navbar-inner sliding">
<div class="title">カレンダー</div>
<div class="right">
<a href="/form/new/" class="link">
<i class="f7-icons">plus</i>
</a>
</div>
</div>
</div>
フォーム画面について
フォーム画面は予定のタイトルと詳細、予定開始日時、終了日時などを入力して登録を行います。
<div class="list no-hairlines-md">
<form id="schedule">
<ul>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-input-wrap">
<input type="text" id="startDate" name="startDate" @change=${setEndDate} placeholder="日時" value="${targetDate}" />
<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="text" id="endDate" name="endDate" placeholder="終了日時" />
<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="text" name="title" placeholder="タイトル" value="${schedule.title}" />
<span class="input-clear-button"></span>
</div>
</div>
</li>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-input-wrap">
<textarea class="resizable" name="body">${schedule.body}</textarea>
<span class="input-clear-button"></span>
</div>
</div>
</li>
</ul>
<div class="block block-strong">
<p class="row">
<a href="#" @click=${saveSchedule} class="col button">保存</a>
</p>
${ schedule.objectId ?
$h`<p class="row">
<a href="#" @click=${deleteSchedule} class="col button">削除</a>
</p>`
: ''
}
</div>
</form>
</div>
データの準備
まず新規データか既存データかの判別を行います。これは画面遷移時のIDによって判別できます。
// ストアから選択された日付とスケジュール一覧を取得
const { schedules, selectedDate } = $store.getters;
// データストアのクラスを準備
const Schedule = ncmb.DataStore('Schedule');
// 既存データの修正か、新規データの追加かを判定
const schedule = props.id === 'new' ? new Schedule() : schedules.value.find(s => s.objectId === props.id);
既存データの場合は /form/new/
ですが、既存データの場合は /form/:objectId
になっています。この objectId
はデータによって変わる、データのユニークキーになります。
以下は calendar.html
の予定一覧表示部分です。
${ schedules.value
.filter(schedule => moment(schedule.startDate.iso).format('YYYY-MM-DD') === moment(selectedDate.value).format('YYYY-MM-DD'))
.map(schedule => $h`
<li>
<a href="/form/${schedule.objectId}/" class="item-link item-content">
<div class="item-inner">
<div class="item-title-row">
<div class="item-title">${schedule.title}</div>
<div class="item-after">
${moment(schedule.startDate.iso).format('HH:mm')}〜${moment(schedule.endDate.iso).format('HH:mm')}
</div>
</div>
<div class="item-text">
${schedule.body}
</div>
</div>
</a>
</li>
`)}
そしてデータに対してACLを設定します。ACLはデータをセキュアに扱うために必要です。今回はデータを作成した本人のみ、読み書きできるようにします。
// ユーザーだけしかアクセスできないようにする
const user = ncmb.User.getCurrentUser();
const acl = new ncmb.Acl();
acl.setUserReadAccess(user, true);
acl.setUserWriteAccess(user, true);
schedule.set('acl', acl);
データの保存処理
データの保存は saveSchedule
関数で行っています。入力値の中でも開始日時・終了日時はDateオブジェクトに変換してセットします。
そして既存データ(objectIdがある)は update
メソッド、新規データは save
メソッドでデータを保存します。
// 予定の保存処理
const saveSchedule = async () => {
// 入力値の取得
const params = app.form.convertToData($('form#schedule'));
// 入力値をデータストアのインスタンスにセット
for (const key in params) {
// 日付は日付オブジェクトに変換する(それ以外は文字列のまま)
const value = ['startDate', 'endDate'].includes(key) ? new Date(params[key]) : params[key];
// データストアのインスタンスにセット
schedule.set(key, value);
}
// データストアへの保存
await (schedule.objectId ? schedule.update() : schedule.save());
// カレンダー画面に戻る
$f7router.back();
};
予定の削除
予定を削除するのは delete
メソッドになります。確認ダイアログを出して、データを削除します。
// 予定の削除処理
const deleteSchedule = () => {
// 確認ダイアログを表示
app.dialog.confirm('削除しますか?', () => {
// 予定の削除
schedule.delete();
// カレンダー画面に戻る
$f7router.back();
});
};
まとめ
ここまでの流れでカレンダーアプリの完成です。NCMBのデータストアでは日付や文字列(他にも数字や配列、真偽値、オブジェクト、位置情報)など様々な形式でデータの保存・取得ができます。ぜひ柔軟に皆さんのアプリに活かしてください。