皆さま、こんにちは
ブレイブソフトのシローです。
仕事としてサーバサイドの保守がメインになりつつ、「あれ?最近、 開発やってなくね?」ということで、
開発力を取り戻すことを目的として、とりあえずTodoアプリを作ることにしました。
その際に、色々試行錯誤したよ、という内容です。
Todoアプリとは?
Todoアプリとは、簡単にいうと「タスク管理を行うツール」です。
例として、Trelloみたいものがあります。
なぜTodoアプリ?
実際のサービスの開発では、「要件定義」や「画面設計」だの開発以外に考えることが多いです。
その点、Todoアプリの開発は機能や画面設計がとてもシンプルです。
Todoアプリの主な機能は
- タスクを追加する
- タスクを編集する
- タスクを削除する
- タスク一覧を表示する
くらいですし、
画面も基本的に一つで済みます。
なので、サービスの考案などに多くの時間をかけず、開発に注力するには向いていると思います。
また同時にフロントエンド、バックエンド、デザインについて満遍なく触れますので、総合的な開発力が身につくと思っています。
作ったもの
先に、今回作ったものを見せようと思います。

一応プロジェクトのソースコードはこちらです。
機能はごく普通のTodoアプリ・・・にするのはちょっとシンプルすぎるので、
時間になると、「そろそろ期限切れだよ!!」みたいな通知をしてくれる機能をつけてみました。
要件定義
シンプルです。
- タスクを追加する
- タスクを編集する
- タスクを削除する
- タスク一覧を表示する
- 期限切れになる前に通知する時間(30分前とか)を設定する
- タスクが期限切れになりそうなら通知する
- タスクの期限が切れると再通知する
画面設計
1ページだからすごく楽です。
draw.ioでも使ってささっと、ドラフトを作りました。

実際のアプリとは色々と違いますが、大体のイメージさえ出来ていればいいんです。
実装方法
まず、
Web系にするか、ネイティブ系にするか
Webで作成すると誰からもアクセスされちゃうので、認証機能が必要になる。そして、セキュアにするほど認証は面倒・・・
また、オンラインでなくても動作したいという考えのもと、ネイティブ系になりました。
次
デスクトップにするか、スマホアプリにするか
僕は使える言語がWeb系に特化しているので、スマホアプリならCordova、
デスクトップアプリならElectron
になるんですが、「スマホでタスクチェックはやらないかな」という結論のもと、Electronでデスクトップアプリを作成することにしました。
Vue.jsも使ってみた
今までフロントはjQueryばかり使ってきたのですが、
フレームワークを利用した開発を積んで置けば、
可能性広がりそうだなと思い、Vue.jsを利用してみようと思いました。
また、jsでの開発を快適に行うために、babelやwebpackも利用しました。
データベースをどうするか
アプリケーションから外部のデータベースにアクセスするよりも、
アプリ内部に組み込めるような形式でデータベースを利用したい。
また、複雑な機能を持たないため、RDBにするよりも、json形式でデータを更新できるNoSQLが良いと思い。
NeDBを使うことにしました。(使い方はここ見ると良いカモです。)
以上をまとめると、
- ネイティブのデスクトップアプリ
- ソフトウェアフレームワークでElectronを使用
- フロントサイドのフレームワークでVue.jsを使用
- データベースはNeDBを使用
となりました。
プロジェクト構成
結果としてこうなっています。
|--data.db #NeDB用ファイル
|--database.js #NeDBのコントローラ
|--index.html #アプリケーション表示用HTML
|--js
| |--bundle.js #アプリケーションが利用するwebpack圧縮後のjsファイル
|--main.js #Electron起動ファイル
|--node_modules #モジュール群
|--notifier.js #通知用コントローラ
|--package.json #npm 設定
|--parametor.json #アプリ内変数
|--setting.js #アプリ内変数のコントローラ
|--src #フロント用の開発ソースファイル群(webpackで圧縮されてbundle.jsになる)
| |--app.js
| |--vue
| | |--app.vue
| | |--css
| | | |--header.css
| | | |--style.css
| | | |--task.css
| | |--js
| | | |--app.js
|--webpack.config.js #webpack設定ファイル
実装
アプリケーションの実装でコアとなった部分についてです。
Electronの起動
何はともあれ、Electron実行しないと始まりません、起動用スクリプト: main.js,レンダー用ファイル: index.htmlに以下を記述します。
main.js
const electron = require('electron')
const path = require('path')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow(
{
width: 1200,
height: 800
}
)
mainWindow.loadURL(path.join('file://', __dirname, 'index.html'))
mainWindow.isMinimized()
})
app.on('window-all-closed', () => {
app.quit()
})
index.html
<!doctype html>
<html>
<head>
<meta charset ="utf-8" />
<title>TODO Notifire</title>
<link rel="stylesheet" href="./css/style.css" />
</head>
<body>
<div id="app">
</div>
<script src="./js/bundle.js"></script>
</body>
</html>
これらのファイルを同じ階層に置いて”electron .”と実行すると実際にアプリケーションが立ち上がる用になります。
フロントの実装
index.htmlからはbundle.jsを読み込んでいます。
このファイルはsrc以下のファイルをwebpackで圧縮して出力したものです。
フロントの実装はVue.jsで行いましたが、一からデザインを作るのも面倒なので、bootstrapみたいなElement-uiという、
コンポーネントを利用して実装しました。
/src/vue/app.vue
<template>
<div>
<section id="header" style="background: 'gray'">
<h1>TODO Notifire</h1>
<el-button id="add-new-task" v-on:click="addNewTask()">New</el-button>
<span>Notify Time: </span>
<el-time-select
v-model="notifyInterval"
v-bind:picker-options="{
start: '00:00',
step: '00:15',
end: '23:45'
}"
v-on:change="changeNotifyInterval()"
placeholder="Select notify time">
</el-time-select>
</section>
<section id="content">
<ul id="task-list">
<li v-for="(task, index) in taskList">
<span v-bind:style="{ color: task.statusColor}">{{ task.statusText }}</span>
<el-input class="task-name" type="text" v-model="task.name" v-on:change="changeTaskName(index)"/>
<el-date-picker
v-model="task.limitDateTime"
v-on:change="changeLimitDateTime(index)"
type="datetime"
placeholder="Select limit datetime">
</el-date-picker>
<el-button type="primary" icon="el-icon-delete" class="remove-task" v-on:click="removeTask(index)"></el-button>
</li>
</ul>
</section>
</div>
</template>
<script src="./js/app.js"></script>
通知の実装
“node-notifier”という、node.jsによる通知機能のモジュールを使いました。(モジュールについて詳しくはここ)
これを使って、期限切れそうになると通知する用にします。
notifier.js
const NN = require('node-notifier')
module.exports = {
notification: (title = 'Todo Notifier', message = 'Sample Notification') => {
NN.notify({
title,
message,
wait: true
})
}
}
これをタスクが切れそうなタイミングで発火するようにします。
タスクの締め切り時刻を監視して、締め切り前、締め切り後になったらnotifier
/src/vue/js/app.js
import { remote } from 'electron'
const notify = remote.require('./notifier')
const notification = (title, message) => {
notify.notification(title, message)
}
const database = remote.require('./database')
const setting = remote.require('./setting')
const taskStatus = {
todo: { text: 'Todo', color: '#39ff64' },
notified: { text: 'Notified', color: '#ffec2a' },
expired: { text: 'Expired', color: '#ff2a2a' }
}
export default {
mounted: function () {
// 締め切り直前と締め切り後のタスクを通知する
setInterval(() => {
let notifyInterval = (() => {
let strs = this.notifyInterval.split(':')
let hours = parseInt(strs[0], 10) * 60 * 60 * 1000
let minutes = parseInt(strs[1], 10) * 60 * 1000
return hours + minutes
})()
for (let task of this.taskList) {
if (!task.name || !task.limitDateTime) continue
let now = Date.parse((new Date()))
let limitDateTime = Date.parse(task.limitDateTime)
let diff = limitDateTime - now
if (diff < notifyInterval && diff > 0) {
if (task.notified) continue
let title = 'そろそろタスクの締め切り前です!'
let message = task.name
let doc = {
expired: true,
statusText: taskStatus.notified.text,
statusColor: taskStatus.notified.color
}
database.updateData(doc, { _id: task._id }, false, (res) => {
if (!res) {
console.log('Failed to change mode')
} else {
task.notified = true
task.statusText = taskStatus.notified.text
task.statusColor = taskStatus.notified.color
notification(title, message)
}
})
} else if (diff < 0) {
if (task.expired) continue
let title = '締め切りです!'
let message = task.name
let doc = {
expired: true,
statusText: taskStatus.expired.text,
statusColor: taskStatus.expired.color
}
database.updateData(doc, { _id: task._id }, false, (res) => {
if (!res) {
console.log('Failed to change mode')
} else {
task.expired = true
task.statusText = taskStatus.expired.text
task.statusColor = taskStatus.expired.color
notification(title, message)
}
})
} else {
let doc = {
notified: false,
expired: false,
statusText: taskStatus.todo.text,
statusColor: taskStatus.todo.color
}
database.updateData(doc, { _id: task._id }, false, (res) => {
if (!res) {
console.log('Failed to change mode')
} else {
task.expired = false
task.notified = false
task.statusText = taskStatus.todo.text
task.statusColor = taskStatus.todo.color
}
})
}
}
}, this.taskCheckInterval)
.
.
.
通知の様子
期限切れそうになタスク様子

アプリの表示

最後に
新しい技術を調べて実装経験を積むためにも、Todoアプリの作成はおすすめです。
投稿者プロフィール
- eventosの開発をやっているエンジニアです。
