NCMBとMonaca、Framework7を使ってカレンダーアプリを作る（その1：画面の説明とSDKの導入）

NCMBとMonacaを使ってカレンダーアプリを作ります。予定を登録したり、Framework7のカレンダーコンポーネントを使って予定を表示できるというアプリです。

まず最初の記事では画面の説明とSDKの導入までを進めます。

## コードについて

今回のコードは[NCMBMania/monaca-calendar-app](https://github.com/NCMBMania/monaca-calendar-app)にアップロードしてあります。実装時の参考にしてください。

## 利用技術について

今回は次のような組み合わせになっています。

- Monaca
- Framework7
- moment.js（Dateオブジェクト操作用）
- NCMB JavaScript SDK

## 仕様について

今回はID/パスワード認証を利用しています。カレンダー表示についてはFramework7の[Calendar / Datepicker | Framework7 Documentation](https://framework7.io/docs/calendar)を利用しています。また、Framework7はVanilla JSで、VueやReactなどのUIフレームワークは利用していません。

## 利用する機能について

カレンダーアプリで利用するNCMBの機能は次の通りです。

- 会員管理
	- ID/パスワード認証
- データストア
  - 予定の登録・更新・削除
  - 予定の検索

## 画面について

今回は以下の3つの画面があります。

### www/pages/login.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/6d552b74-c16a-b9e9-17e7-64894367b07d.png)

ログイン画面です。ID/パスワードを使って認証します。今回はユーザー登録とログイン画面を兼ねています。

```html
<div class="block-title">ユーザーIDとパスワードを入力してください</div>
<form id="login">
	<div class="list no-hairlines-md">
		<ul>
			<li>
				<div class="item-content item-input">
					<div class="item-inner">
						<div class="item-title item-label">ユーザーID</div>
						<div class="item-input-wrap">
							<input type="text" name="userName" placeholder="お名前" value="goofmint" required/>
						</div>
					</div>
				</div>
			</li>
			<li>
				<div class="item-content item-input">
					<div class="item-inner">
						<div class="item-title item-label">パスワード</div>
						<div class="item-input-wrap">
							<input type="password" name="password" placeholder="パスワード" value="goofmint" required/>
						</div>
					</div>
				</div>
			</li>
		</ul>
	</div>
	<div class="block block-strong">
		<p class="row">
			<a href="#" @click=${login} class="col button">ログイン or ユーザー登録</a>
		</p>
	</div>
</form>
```

### www/pages/calendar.html

![0409 localhost - 1120115748.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/0293fa58-7fd8-365b-4af3-8ef2bd1b95b5.jpeg)


カレンダー画面です。NCMBのデータストアから予定を取得して、画面にドット表示で反映させます。日付を選択すると、該当日の予定を一覧表示します。

```html
<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>
```

### www/pages/form.html

![0409 localhost - 1120120122.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/f92707b4-279c-3775-ef87-28aae02f6e69.jpeg)


予定を登録・更新または削除する画面です。日時の入力はFramework7のカレンダーコンポーネントを利用しています。

```html
<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>
```

### SDKのインストール

今回はMonacaのJS/CSSコンポーネントの追加と削除より、NCMBを追加します。アプリのテンプレートはFramework7のJavaScript版（VueやReactではなく）を選択しています。

## NCMBのAPIキーを取得

[mBaaSでサーバー開発不要！ | ニフクラ mobile backend](https://mbaas.nifcloud.com/)にてアプリを作成し、アプリケーションキーとクライアントキーを作成します。

## js/config.jsonの作成

js/config.jsonを作成し、その中に先ほど取得したNCMBのAPIキーを設定します。内容は次のようになります。

```json
{
	"applicationKey": "YOUR_APPLICATION_KEY",
	"clientKey": "YOUR_CLIENT_KEY"
}
```

## 初期化

初期化は `js/app.js` にて行います。config.jsonを読み込む関係上、非同期処理内にて行います。cordovaの有無（アプリまたはプレビューの違いを検知）によって初期化時のイベント処理を変えています。

```js
// NCMBの初期化用
const event = window.cordova ? 'deviceready' : 'DOMContentLoaded';
document.addEventListener(event, async (e) => {
	// この中に処理を書きます
});
```

config.jsonの内容を読み込んで、NCMBとFramework7の初期化を行います。

```js
window.config = await (await fetch('./js/config.json')).json();
window.ncmb = new NCMB(config.applicationKey, config.clientKey);
window.app = new Framework7({
	name: 'My App', // App name
	theme: 'auto', // Automatic theme detection
	el: '#app', // App root element
	// App store
	store: store,
	// App routes
	routes: routes,
});
```

これでNCMBの初期化が完了します。

## ルーティング設定

今回はチャット画面、ログイン画面の2つがあります。これを `js/routes.js` に定義します。

```js
const routes = [
  {
    path: '/',
    url: './index.html',
  },
  // カレンダー画面
  {
    path: '/home/',
    componentUrl: './pages/calendar.html',
    beforeEnter,
  },
  // ログイン画面
  {
    path: '/login/',
    componentUrl: './pages/login.html',
  },
  // 予定追加・更新画面
  {
    path: '/form/:id/',
    componentUrl: './pages/form.html',
  },
  // Default route (404 page). MUST BE THE LAST
  {
    path: '(.*)',
    url: './pages/404.html',
  },
];
```

### 認証していない場合の判定処理

上記のルーティングで `beforeEnter` を指定しています。ここで、カレンダー画面を表示する前に、認証状態を確認します。そしてログインしていない場合には、ログイン画面に遷移します。

```js
// routes.jsに記述
const beforeEnter = async ({resolve, reject, router}) => {
  // 現在ログインしているユーザー情報を取得
  const currentUser = ncmb.User.getCurrentUser();
  // 認証していない場合はnullになる
  if (!currentUser) {
    // rejectを実行
    reject();
    // 認証画面に遷移
    router.navigate('/login/');
    // 処理完了
    return;
  }
  try {
    // データストアにダミーアクセス
    await ncmb.DataStore('Hello').fetch();
    // アクセスできればresolveを実行（元々指定されている画面を表示）
    resolve();
  } catch (e) {
    // 駄目だった場合は認証情報を削除
    localStorage.removeItem("NCMB/"+ncmb.apikey+"/currentUser");
    // rejectを実行
    reject();
    // 認証画面に遷移
    router.navigate('/login/');
  }  
};
```

## まとめ

今回はカレンダーアプリの仕様と画面、NCMBの初期化までを解説しました。次は[ログイン処理を実装]()します。