LoginSignup
2
0

More than 1 year has passed since last update.

Web3.0検証(16)-MeteorでTODO管理アプリの開発(タスク絞込み機能とUI改善)

Last updated at Posted at 2022-05-22
[前回] 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のメインdivclassName属性を追加
      • 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)
  • デバイスのツールバーを切り替えアイコンをクリック
  • デバイスを選択
image.png

見た目がアプリぽっくなりました。

タスクの絞込み機能

作業内容

  • タスクをフィルタリング可能にし、アプリをもっとインタラクティブに
  • タスクをステータスでフィルタリングし、保留中タスクの数を表示

完了タスクをリストから表示/非表示にするボタンを追加

作業内容

  • ReactのフックuseStateを使って、ボタン状態を保持
    • useStateフックをimport
      • フックは常にコンポーネントの上部に追加することをお勧め
        • 常に同じ順序で実行可能
    • useStateは、2つの項目を含む配列を返す
      • 1番目の要素は、state
      • 2番目の要素は、状態更新用setter関数
      • Array destructuring(配列の分割代入で複数値を同時代入)を使って、2つの戻り値を同時にconst変数で受け取る
  • タスクフォームの下に、現在状態に基づき異なる内容を表示する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クエリのselectorfilterを追加
      • isCheckedtrueでないすべてのタスクを取得
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拡張機能

  • Meteor DevTools Evolved拡張機能を使用
    • Mini Mongoデータを視覚化
    • アプリのデバッグ可能
      image.png
    • Meteorとサーバー間の送受信メッセージから、Meteor動作を確認可能
      image.png

アプリバーに保留中タスクの数を表示

  • 保留中タスクがない場合は、アプリバーにゼロを追加しない
  • 保留中タスクの数を取得するトラッカーをもう一つ追加
    • 1つのuseTrackerのみ使用し、二つの検索結果のプロパティを持つオブジェクトを返すことも可能
    • ここでは、コード理解のため2つのトラッカーに分けて作成
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>
  );
};

再度、ブラウザからアプリを確認

絞込み前 絞込み後
image.png image.png

おわりに

タスクのフィルタリング機能を追加しました。
次回も続きます。お楽しみに。

[次回] Web3.0検証(17)-MeteorでTODO管理アプリの開発(ユーザーアカウント機能)
2
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
2
0