0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NCMBとMonaca、Framework7を使ってカレンダーアプリを作る(その3:予定の表示と登録)

Last updated at Posted at 2022-11-21

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;
    },
  },
});

画面について

0409 localhost - 1120115748.jpg

カレンダー画面は以下のようになります。カレンダーの表示自体は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>

フォーム画面について

0409 localhost - 1120120122.jpg

フォーム画面は予定のタイトルと詳細、予定開始日時、終了日時などを入力して登録を行います。

<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のデータストアでは日付や文字列(他にも数字や配列、真偽値、オブジェクト、位置情報)など様々な形式でデータの保存・取得ができます。ぜひ柔軟に皆さんのアプリに活かしてください。

mBaaSでサーバー開発不要! | ニフクラ mobile backend

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?