Drayman、MongoDBとテールウィンドCSSでリアルタイムtodoアプリを構築する


このガイドでは、リアルタイムのtodoアプリケーションを構築するDrayman , MongoDB and Tailwind CSS .
あなたが定期的なツールを使用している場合は、エンドポイント、クライアントのアプリケーションいくつかの近代的なフレームワークやちょうどバニラJavaScriptを使用してサーバーを作成する必要があります.しかし、Draymanでは、100行のコードでただ一つのスクリプトになります.
あなたがDraymanに新しいならば、あなたは我々のところに行くことができますofficial docs それがどのように動作するか、他のガイドを読むことを確認してください.

必要条件
このガイドでは、
  • installed Drayman project locally または、あなたはnew.drayman.io ;
  • をインストールするChange Stream 機能を有効にします.このガイドの場合は、無料のクラウドMongoDBデータベースを作成することができますhere . デフォルトで有効になった変更ストリーム機能を取得する方法です.
  • DrayManプロジェクトが準備され、あなたがMongoDBを持っていると実行すると、我々はTrewind CSSを追加することによって我々のtodoアプリを開発を開始することができます.

    Cookwind CSSの設定
    まず、インストールする必要がありますautoprefixer and tailwindcss :
    npm install -D tailwindcss@latest autoprefixer@latest
    
    次に、生成tailwind.config.js and postcss.config.js このコマンドを含むファイル
    npx tailwindcss init -p
    
    今すぐ変更tailwind.config.js コンパイル時に使われていないCSSクラスを除外するファイル
    module.exports = {
      purge: [
        "./src/**/*.tsx",
      ],
      darkMode: false,
      theme: {
        extend: {},
      },
      variants: {
        extend: {},
      },
      plugins: [],
    };
    
    メインCSSファイルをsrc/styles.css このコードを追加します.
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    
    最後のステップは変更することですpublic/index.html 生成されたCSSファイルを含むファイル.Draymanはそれを生成するpublic/styles.css ファイル
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <title>Drayman Framework</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <script src="/drayman-framework-client.js"></script>
        <link rel="stylesheet" href="styles.css" />
      </head>
    
      <body>
        <drayman-element component="home"></drayman-element>
    
        <script>
          initializeDraymanFramework();
        </script>
      </body>
    </html>
    

    ToDoアプリを作成する
    私たちはTruwind CSSクラスを使用して基本的なUIを追加することから始めます.

    基本UIの追加
    このコードをsrc/components.home.tsx :
    export const component: DraymanComponent = async () => {
      return async () => {
        return (
          <div class="flex justify-center items-center">
            <div class="flex flex-col pt-8 lg:w-2/5 sm:w-3/5 w-11/12 gap-4">
              <div class="flex gap-2">
                <input
                  placeholder="New Todo"
                  class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                />
                <button
                  class={`bg-blue-500 text-white font-bold py-2 px-4 rounded`}
                >
                  Save
                </button>
              </div>
              <div class="flex items-center gap-2">
                <div
                  class={`flex flex-grow items-center gap-2 bg-gray-200 p-2 rounded cursor-pointer`}
                >
                  <input class="flex-none" type="checkbox" />
                  <div class="flex-grow">Grab a coffee</div>
                </div>
                <button class="flex-none bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">
                  Delete
                </button>
              </div>
            </div>
          </div>
        );
      };
    };
    
    Toowind CSSが正常に初期化された場合は、この結果をブラウザに表示する必要があります.


    MongoDBサポートの追加
    このガイドのために我々は使用されますChange Stream 機能は、我々のtodoリストをリアルタイムで動作させるMongoDBの機能.何かがデータベースで変更されるならば、我々のアプリケーションはこれらの変化を反映します.修正しましょうsrc/components/home.tsx コンポーネントと詳細については、スクリプトの中で何が起こっているかを詳しく見てください.
    import { MongoClient, ObjectId } from "mongodb";
    
    interface Todo {
      _id: ObjectId;
      text: string;
      done: boolean;
    }
    
    export const component: DraymanComponent = async ({
      forceUpdate,
      ComponentInstance,
    }) => {
      const uri = "YOUR_MONGODB_CONNECTION_STRING";
      const client = new MongoClient(uri);
      await client.connect();
      const db = client.db("todo");
      const todoListCollection = db.collection<Todo>("list");
    
      let todos = await todoListCollection.find().toArray();
    
      todoListCollection.watch().on("change", async (x) => {
        if (x.operationType === "insert") {
          todos.push(x.fullDocument as Todo);
        } else if (x.operationType === "update") {
          todos = todos.map((todo) => {
            if (todo._id.equals(x.documentKey._id)) {
              return { ...todo, ...x.updateDescription.updatedFields };
            }
            return todo;
          });
        } else if (x.operationType === "delete") {
          todos = todos.filter((todo) => !todo._id.equals(x.documentKey._id));
        }
        await forceUpdate();
      });
    
      ComponentInstance.onDestroy = async () => {
        await client.close();
      };
    
      return async () => {
        return (
          <div class="flex justify-center items-center">
            <div class="flex flex-col pt-8 lg:w-2/5 sm:w-3/5 w-11/12 gap-4">
              <div class="flex gap-2">
                <input
                  placeholder="New Todo"
                  class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                />
                <button class="bg-blue-500 text-white font-bold py-2 px-4 rounded">
                  Save
                </button>
              </div>
              {todos.map((x) => (
                <div class="flex items-center gap-2">
                  <div
                    class={`flex flex-grow items-center gap-2 ${
                      x.done ? `bg-green-200 line-through` : `bg-gray-200`
                    } p-2 rounded cursor-pointer`}
                  >
                    <input class="flex-none" checked={x.done} type="checkbox" />
                    <div class="flex-grow">{x.text}</div>
                  </div>
                  <button class="flex-none bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">
                    Delete
                  </button>
                </div>
              ))}
            </div>
          </div>
        );
      };
    };
    
    まず、我々のデータベースへの接続を追加しましたYOUR_MONGODB_CONNECTION_STRING を参照してください.そして、それを配列に変換してtodoリストの初期取得を行いました.let todos = await todoListCollection.find().toArray(); .
    その後、データベースへの変更を見てリアルタイム機能を追加しましたtodoListCollection.watch() :
  • on insert 藤堂を押すtodos アレイ
  • on update からの特定の藤堂todos 配列が更新されます
  • on delete からの特定の藤堂todos 配列は削除されます.
  • コンポーネントはこれらの変更を反映しますforceUpdate function -任意の変更が捕捉されると、コンポーネントは再表示されます.
    最後に、我々はonDestroy LifeCycleメソッドを使用して、コンポーネントインスタンスが破壊されたときにデータベースへの接続を閉じます.
    また、マップを介して私たちのUIに変更を行っているtodos todoを実行すると、各todoをレンダリングし、CSSクラスを動的に変更します.
    その結果、データベース内で変更されましたTablePlus この場合、コンポーネントの内部で反映されます.

    最後に、入力フィールドとボタンを期待通りに動作させる.

    UI要素への機能追加
    を修正することから始めます<input> 要素
    // ...
    let newTodo: string;
    // ...
    
    return async () => {
      return (
        // ...
        <input
          placeholder="New Todo"
          class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
          value={newTodo}
          oninput={async ({ value }) => {
            newTodo = value;
            await forceUpdate();
          }}
        />
        // ...
      );
    };
    
    変数newTodo 導入された.ユーザーが何かでタイプするときoninput イベントは、入力値をnewTodo このように真実の単一の源を提供する変数-入力値は常に内側になりますnewTodo .
    今すぐ修正しましょう<button>Save</button> 要素
    <button
      class={`bg-blue-500 text-white font-bold py-2 px-4 rounded ${
        newTodo ? `hover:bg-blue-700` : `opacity-50 cursor-not-allowed`
      }`}
      disabled={!newTodo}
      onclick={async () => {
        await todoListCollection.insertOne({ text: newTodo, done: false });
        newTodo = null;
      }}
    >
      Save
    </button>
    
    修正しましたclass and disabled ユーザーが入力しないときに無効にする属性newTodo が空である).ユーザーがボタンをクリックすると、onclick トリガを取得し、新しいToDoデータベースに挿入されます.また、コールする必要はありませんforceUpdate ここでは、データベースの変更を監視する前に既に管理されていたからです.今何かを入力することができますボタンをクリックし、結果がブラウザに表示されます:

    私たちの最終的なステップを変更することですtodos リストマッピング:
    todos.map((x) => (
      <div class="flex items-center gap-2">
        <div
          onclick={async () => {
            await todoListCollection.updateOne(
              { _id: x._id },
              { $set: { done: !x.done } }
            );
          }}
          class={`flex flex-grow items-center gap-2 ${
            x.done ? `bg-green-200 line-through` : `bg-gray-200`
          } p-2 rounded cursor-pointer`}
        >
          <input class="flex-none" checked={x.done} type="checkbox" />
          <div class="flex-grow">{x.text}</div>
        </div>
        <button
          class="flex-none bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
          onclick={async () => {
            await todoListCollection.deleteOne({ _id: x._id });
          }}
        >
          Delete
        </button>
      </div>
    ));
    
    ここではonclick イベントハンドラ<div> 打たれたものを元に戻すonclick イベントハンドラ<button>Delete</button> をクリックすると、データベースからToDoを削除します.
    私たちのコンポーネントが完了し、複数のタブでこのコンポーネントを使用してページを開くことができます.


    最終成分スクリプト
    import { MongoClient, ObjectId } from "mongodb";
    
    interface Todo {
      _id: ObjectId;
      text: string;
      done: boolean;
    }
    
    export const component: DraymanComponent = async ({
      forceUpdate,
      ComponentInstance,
    }) => {
      const uri = "YOUR_MONGODB_CONNECTION_STRING";
      const client = new MongoClient(uri);
      await client.connect();
      const db = client.db("todo");
      const todoListCollection = db.collection<Todo>("list");
    
      let todos = await todoListCollection.find().toArray();
      let newTodo: string;
    
      todoListCollection.watch().on("change", async (x) => {
        if (x.operationType === "insert") {
          todos.push(x.fullDocument as Todo);
        } else if (x.operationType === "update") {
          todos = todos.map((todo) => {
            if (todo._id.equals(x.documentKey._id)) {
              return { ...todo, ...x.updateDescription.updatedFields };
            }
            return todo;
          });
        } else if (x.operationType === "delete") {
          todos = todos.filter((todo) => !todo._id.equals(x.documentKey._id));
        }
        await forceUpdate();
      });
    
      ComponentInstance.onDestroy = async () => {
        await client.close();
      };
    
      return async () => {
        return (
          <div class="flex justify-center items-center">
            <div class="flex flex-col pt-8 lg:w-2/5 sm:w-3/5 w-11/12 gap-4">
              <div class="flex gap-2">
                <input
                  placeholder="New Todo"
                  class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                  value={newTodo}
                  oninput={async ({ value }) => {
                    newTodo = value;
                    await forceUpdate();
                  }}
                />
                <button
                  class={`bg-blue-500 text-white font-bold py-2 px-4 rounded ${
                    newTodo ? `hover:bg-blue-700` : `opacity-50 cursor-not-allowed`
                  }`}
                  disabled={!newTodo}
                  onclick={async () => {
                    await todoListCollection.insertOne({
                      text: newTodo,
                      done: false,
                    });
                    newTodo = null;
                  }}
                >
                  Save
                </button>
              </div>
              {todos.map((x) => (
                <div class="flex items-center gap-2">
                  <div
                    onclick={async () => {
                      await todoListCollection.updateOne(
                        { _id: x._id },
                        { $set: { done: !x.done } }
                      );
                    }}
                    class={`flex flex-grow items-center gap-2 ${
                      x.done ? `bg-green-200 line-through` : `bg-gray-200`
                    } p-2 rounded cursor-pointer`}
                  >
                    <input class="flex-none" checked={x.done} type="checkbox" />
                    <div class="flex-grow">{x.text}</div>
                  </div>
                  <button
                    class="flex-none bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
                    onclick={async () => {
                      await todoListCollection.deleteOne({ _id: x._id });
                    }}
                  >
                    Delete
                  </button>
                </div>
              ))}
            </div>
          </div>
        );
      };
    };
    

    結論
    我々は、1つのスクリプトの中にコードのちょうど100行でDrayman、MongoDBとTailwind CSSとリアルタイムのToDoアプリを作成している.
    これがあなたに面白いと感じたならばofficial docs DrayManフレームワークに深く飛び込む!