[前回] Web3.0検証(15)-MeteorでTODO管理アプリの開発(タスクの更新/削除)
はじめに
今回は、TODO管理アプリにタスク絞込み機能を追加します。
まずは、アプリUIを改善
スタイルを修正
-
CSS(カスケーディングスタイルシート)とは
- Webページの文字の色や大きさ、背景、配置といったスタイル(見た目)を設定する言語
- ※ Flexboxツールを使用し、UI内要素を分散および整列可能
- 水平/垂直に要素を配置し、柔軟なレイアウトを実現できるCSSレイアウトモジュール
-
修正内容
- 上部にアプリバーを配置
- スクロール可能なコンテンツを配置
- 新タスクを追加するフォーム
- タスクリスト
$ cd simple-todos-react
$ vi client/main.css
client/main.css
body {
font-family: sans-serif;
background-color: #315481;
background-image: linear-gradient(to bottom, #315481, #918e82 100%);
background-attachment: fixed;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 0;
margin: 0;
font-size: 14px;
}
button {
font-weight: bold;
font-size: 1em;
border: none;
color: white;
box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4);
padding: 5px;
cursor: pointer;
}
button:focus {
outline: 0;
}
.app {
display: flex;
flex-direction: column;
height: 100vh;
}
.app-header {
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.main {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: auto;
background: white;
}
.main::-webkit-scrollbar {
width: 0;
height: 0;
background: inherit;
}
header {
background: #d2edf4;
background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%);
padding: 20px 15px 15px 15px;
position: relative;
box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4);
}
.app-bar {
display: flex;
justify-content: space-between;
}
.app-bar h1 {
font-size: 1.5em;
margin: 0;
display: inline-block;
margin-right: 1em;
}
.task-form {
display: flex;
margin: 16px;
}
.task-form > input {
flex-grow: 1;
box-sizing: border-box;
padding: 10px 6px;
background: transparent;
border: 1px solid #aaa;
width: 100%;
font-size: 1em;
margin-right: 16px;
}
.task-form > input:focus {
outline: 0;
}
.task-form > button {
min-width: 100px;
height: 95%;
background-color: #315481;
}
.tasks {
list-style-type: none;
padding-inline-start: 0;
padding-left: 16px;
padding-right: 16px;
margin-block-start: 0;
margin-block-end: 0;
}
.tasks > li {
display: flex;
padding: 16px;
border-bottom: #eee solid 1px;
}
.tasks > li > span {
flex-grow: 1;
}
.tasks > li > button {
justify-self: flex-end;
background-color: #ff3046;
}
スタイルを適用
- 修正内容
- コンポーネント周囲に要素を追加
-
App
のメインdiv
にclassName
属性を追加 -
h1
周りにいくつかdiv
を含むheader
要素を追加 - フォームとリスト周りにメイン
div
を追加 - ※
className
に注意-
className
はCSSで定義したものを指定 - Reactでは
class
の代わりにclassName
使用- ReactはJavaScriptを使用しUIを定義
-
class
がJavaScriptの予約語であるため
-
-
- アプリのタイトルを変更
- コンポーネント周囲に要素を追加
imports/ui/App.jsx
import React from 'react';
import { useTracker } from 'meteor/react-meteor-data';
import { Task } from './Task';
import { TasksCollection } from '/imports/api/TasksCollection';
import { TaskForm } from './TaskForm';
const toggleChecked = ({ _id, isChecked }) => {
TasksCollection.update(_id, {
$set: {
isChecked: !isChecked
}
})
};
const deleteTask = ({ _id }) => TasksCollection.remove(_id);
export const App = () => {
const tasks = useTracker(() => TasksCollection.find({}, { sort: { createdAt: -1 } }).fetch());
return (
<div className="app">
<header>
<div className="app-bar">
<div className="app-header">
<h1>📝️ To Do List</h1>
</div>
</div>
</header>
<div className="main">
<TaskForm />
<ul className="tasks">
{tasks.map(task => (
<Task
key={task._id}
task={task}
onCheckboxClick={toggleChecked}
onDeleteClick={deleteTask}
/>
))}
</ul>
</div>
</div>
);
};
ブラウザでアプリを確認
Meteorアプリを実行
$ cd simple-todos-react
$ meteor run
ブラウザでアプリを確認
- ブラウザから、
http://localhost:3000/
にアクセス - デベロッパーツールを開く(F12)
-
デバイスのツールバーを切り替え
アイコンをクリック - デバイスを選択
見た目がアプリぽっくなりました。
タスクの絞込み機能
作業内容
- タスクをフィルタリング可能にし、アプリをもっとインタラクティブに
- タスクをステータスでフィルタリングし、保留中タスクの数を表示
完了タスクをリストから表示/非表示にするボタンを追加
作業内容
- Reactのフック
useState
を使って、ボタン状態を保持-
useState
フックをimport
- フックは常にコンポーネントの上部に追加することをお勧め
- 常に同じ順序で実行可能
- フックは常にコンポーネントの上部に追加することをお勧め
-
useState
は、2つの項目を含む配列を返す- 1番目の要素は、
state
値 - 2番目の要素は、状態更新用
setter
関数 -
Array destructuring
(配列の分割代入で複数値を同時代入)を使って、2つの戻り値を同時にconst
変数で受け取る
- 1番目の要素は、
-
- タスクフォームの下に、現在状態に基づき異なる内容を表示する
button
を追加
imports/ui/App.jsx
import React, { useState } from 'react';
import { useTracker } from 'meteor/react-meteor-data';
import { Task } from './Task';
import { TasksCollection } from '/imports/api/TasksCollection';
import { TaskForm } from './TaskForm';
const toggleChecked = ({ _id, isChecked }) => {
TasksCollection.update(_id, {
$set: {
isChecked: !isChecked
}
})
};
const deleteTask = ({ _id }) => TasksCollection.remove(_id);
export const App = () => {
const tasks = useTracker(() => TasksCollection.find({}, { sort: { createdAt: -1 } }).fetch());
const [hideCompleted, setHideCompleted] = useState(false);
return (
<div className="app">
<header>
<div className="app-bar">
<div className="app-header">
<h1>📝️ To Do List</h1>
</div>
</div>
</header>
<div className="main">
<TaskForm />
<div className="filter">
<button onClick={() => setHideCompleted(!hideCompleted)}>
{hideCompleted ? 'Show All' : 'Hide Completed'}
</button>
</div>
<ul className="tasks">
{tasks.map(task => (
<Task
key={task._id}
task={task}
onCheckboxClick={toggleChecked}
onDeleteClick={deleteTask}
/>
))}
</ul>
</div>
</div>
);
};
ボタンスタイルを修正
-
client/main.css
の末尾に以下を追記
client/main.css
.filter {
display: flex;
justify-content: center;
}
.filter > button {
background-color: #62807e;
}
タスクのフィルタリング
- 保留中タスクのみを表示
-
Mini Mongo
クエリのselector
にfilter
を追加-
isChecked
がtrue
でないすべてのタスクを取得
-
-
imports/ui/App.jsx
import React, { useState } from 'react';
import { useTracker } from 'meteor/react-meteor-data';
import { Task } from './Task';
import { TasksCollection } from '/imports/api/TasksCollection';
import { TaskForm } from './TaskForm';
const toggleChecked = ({ _id, isChecked }) => {
TasksCollection.update(_id, {
$set: {
isChecked: !isChecked
}
})
};
const deleteTask = ({ _id }) => TasksCollection.remove(_id);
export const App = () => {
const [hideCompleted, setHideCompleted] = useState(false);
const hideCompletedFilter = { isChecked: { $ne: true } };
const tasks = useTracker(() =>
TasksCollection.find(hideCompleted ? hideCompletedFilter : {}, {
sort: { createdAt: -1 },
}).fetch()
);
return (
<div className="app">
<header>
<div className="app-bar">
<div className="app-header">
<h1>📝️ To Do List</h1>
</div>
</div>
</header>
<div className="main">
<TaskForm />
<div className="filter">
<button onClick={() => setHideCompleted(!hideCompleted)}>
{hideCompleted ? 'Show All' : 'Hide Completed'}
</button>
</div>
<ul className="tasks">
{tasks.map(task => (
<Task
key={task._id}
task={task}
onCheckboxClick={toggleChecked}
onDeleteClick={deleteTask}
/>
))}
</ul>
</div>
</div>
);
};
Meteor DevTools
拡張機能
アプリバーに保留中タスクの数を表示
- 保留中タスクがない場合は、アプリバーにゼロを追加しない
- 保留中タスクの数を取得するトラッカーをもう一つ追加
- 1つの
useTracker
のみ使用し、二つの検索結果のプロパティを持つオブジェクトを返すことも可能 - ここでは、コード理解のため2つのトラッカーに分けて作成
- 1つの
imports/ui/App.jsx
import React, { useState } from 'react';
import { useTracker } from 'meteor/react-meteor-data';
import { Task } from './Task';
import { TasksCollection } from '/imports/api/TasksCollection';
import { TaskForm } from './TaskForm';
const toggleChecked = ({ _id, isChecked }) => {
TasksCollection.update(_id, {
$set: {
isChecked: !isChecked
}
})
};
const deleteTask = ({ _id }) => TasksCollection.remove(_id);
export const App = () => {
const [hideCompleted, setHideCompleted] = useState(false);
const hideCompletedFilter = { isChecked: { $ne: true } };
const tasks = useTracker(() =>
TasksCollection.find(hideCompleted ? hideCompletedFilter : {}, {
sort: { createdAt: -1 },
}).fetch()
);
const pendingTasksCount = useTracker(() =>
TasksCollection.find(hideCompletedFilter).count()
);
const pendingTasksTitle = `${
pendingTasksCount ? ` (${pendingTasksCount})` : ''
}`;
return (
<div className="app">
<header>
<div className="app-bar">
<div className="app-header">
<h1>
📝️ To Do List
{pendingTasksTitle}
</h1>
</div>
</div>
</header>
<div className="main">
<TaskForm />
<div className="filter">
<button onClick={() => setHideCompleted(!hideCompleted)}>
{hideCompleted ? 'Show All' : 'Hide Completed'}
</button>
</div>
<ul className="tasks">
{tasks.map(task => (
<Task
key={task._id}
task={task}
onCheckboxClick={toggleChecked}
onDeleteClick={deleteTask}
/>
))}
</ul>
</div>
</div>
);
};
再度、ブラウザからアプリを確認
絞込み前 | 絞込み後 |
---|---|
おわりに
タスクのフィルタリング機能を追加しました。
次回も続きます。お楽しみに。