使用Aurelia和GraphQL创建教程中的Todo应用程序

首先

在Aurelia的官方网站上,有一个关于创建Todo应用的教程(https://aurelia.io/docs/tutorials/creating-a-todo-app/)。
这次,我想要通过GraphQL + Apollo来自定义管理Todo在数据库中的能力。
基本结构与官方教程相同。

我已将完成系列的源代码放在Github上。

环境

バージョンaurelia-cliv1.2.2hasurav1.0.0-beta.10

环境建设

奥蕾利亚项目

使用aurelia-cli创建一个用于Todo应用的项目。
如果尚未安装aurelia-cli,则进行安装。

npm install -g aurelia-cli

使用au new命令创建项目。
这次,项目名称为aurelia-apollo-todo,并且其他选项将使用默认值创建。

au new

? Please enter a name for your new project: aurelia-apollo-todo
? Would you like to use the default setup or customize your choices? …
▸ Default ESNext App
? Would you like to install all the npm dependencies? …
▸ Yes, use Yarn

完成项目后,让我们启动一下。

cd aurelia-apollo-todo
au run

当你访问 http://localhost:8080,会显示出“你好,世界!”。

GraphQL服务器

我们将使用Hasura作为GraphQL服务器。
为了在本地环境中运行Hasura,提供了docker-compose.yml文件。

wget https://raw.githubusercontent.com/hasura/graphql-engine/master/install-manifests/docker-compose/docker-compose.yaml

顺便说一下,我们将在Docker Compose中运行刚刚创建的Aurelia项目。

FROM node:10.17.0-alpine

WORKDIR /app

COPY ./package.json .
COPY ./yarn.lock .

RUN apk update && \
  yarn global add aurelia-cli && \
  yarn
version: '3.6'
services:
  web:
    build: .
    ports:
      - 8000:8000
    volumes:
      - .:/app
    stdin_open: true
    tty: true
    command: yarn start --host 0.0.0.0
  postgres:
    image: postgres
    volumes:
      - db_data:/var/lib/postgresql/data
  graphql-engine:
    image: hasura/graphql-engine:v1.0.0-beta.10
    ports:
      - "8080:8080"
    depends_on:
      - "postgres"
    environment:
      HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:@postgres:5432/postgres
      HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console
      HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
      ## uncomment next line to set an admin secret
      # HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey
volumes:
  db_data:
"platform": {
  "hmr": false,
  "open": false,
-  "port": 8000,
+  "port": 8000,
  "host": "localhost",
  "output": "dist"
},

由于Aurelia的项目和Hasura具有相同的端口号,因此将Aurelia的端口号更改为8000。
一旦创建了Dockerfile和docker-compose.yml,就可以启动Docker Compose。

docker-compose up -d --build

当访问 http://localhost:8000 时,会显示与之前相同的『Hello World!』。
当访问 http://localhost:8080 时,会显示 Hasura 的界面。

image.png

创建todos表

创建一个用于注册Todo的表格。

    • uuid ・・・一意になるID

 

    • description ・・・Todoの内容

 

    done・・・完了したかどうか

为了注册内容和记录是否完成,准备一个名为“description”的列和一个名为“done”的列。
选择“DATA – Create Table”,实际创建一个todos表。

image.png
image.png

当您输入必要的项目并点击”Add Table”时,将会创建一个名为”todos”的表格。

咱们先往任务应用中添加一些样本数据,以便展示。
选择“插入行”选项卡,然后即可进行数据录入。

image.png

无论内容是什么,但在描述中输入“示例任务”,并选择假设为False。
完成输入后,点击保存即可将数据注册。

image.png

创建待办事项应用程序

我们将创建一个待办事项应用程序。
首先安装所需的库。

docker-compose exec web yarn add apollo-client apollo-cache-inmemory apollo-link-http graphql-tag graphql

我将在webpack.config.js中添加loader的配置。

devServer: {
  contentBase: outDir,
  // serve index.html for all 404 (required for push-state)
  historyApiFallback: true,
  hot: hmr || project.platform.hmr,
  port: port || project.platform.port,
  host: host,
+  watchOptions: {
+    aggregateTimeout: 300,
+    poll: 1000
+  }
},
devtool: production ? 'nosources-source-map' : 'cheap-module-eval-source-map',
module: {
  rules: [
(略)

+    {
+      test: /\.(graphql|gql)$/,
+      exclude: /node_modules/,
+      loader: 'graphql-tag/loader'
+    }
  ]
}

由于更改了设置,我们将重新启动Web容器。

docker-compose restart web

显示Todo列表

我们将从项目创建时就存在的src/app.js和src/app.html文件进行以下更改。

import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import TODOS from './graphql/Todos.gql';

const cache = new InMemoryCache();
const link = new HttpLink({
  uri: 'http://localhost:8080/v1/graphql'
});

const client = new ApolloClient({
  cache,
  link
});

export class App {
  heading = 'Todos';
  todos = [];

  constructor() {
    client.query({
      query: TODOS
    }).then(data => this.todos = data.data.todos);
  }
}
<template>
  <h1>${heading}</h1>

  <ul>
    <li repeat.for="todo of todos">
      <input type="checkbox" checked.bind="todo.done">
      <span>
        ${todo.description}
      </span>
    </li>
  </ul>
</template>

从’./graphql/Todos.gql’导入TODOS的文件是定义GraphQL查询的文件。
这是一个查询,用于获取todos表中注册的所有Todo。

query Todos{
  todos {
    uuid
    description
    done
  }
}

当访问http://localhost:8000时,应该显示先前注册的数据。

image.png

在src/app.js的第20至22行,执行查询并获取数据。
数据以数组形式存储在todos中,在src/app.html的第5行的repeat.for=”todo of todos”中逐一取出,并生成li元素。

添加新的Todo

为了能够添加新的待办事项,我们需要添加文本框和按钮。


<template>
  <h1>${heading}</h1>

+  <form submit.trigger="addTodo()">
+    <input type="text" value.bind="todoDescription">
+    <button type="submit" disabled.bind="!todoDescription">Add Todo</button>
+  </form>

  <ul>
    <li repeat.for="todo of todos">
      <input type="checkbox" checked.bind="todo.done">
      <span>
        ${todo.description}
      </span>
    </li>
  </ul>

</template>
mutation ($description: String) {
  insert_todos(objects: {description: $description, done: false}) {
    returning {
      uuid
      description
      done
    }
  }
}
+ import INSERT_TODO from './graphql/InsertTodo.gql';
(略)

export class App {
  heading = 'Todos';
  todos = [];
+  todoDescription = ''
(略)

+  addTodo() {
+    if (this.todoDescription) {
+      client.mutate({
+        mutation: INSERT_TODO,
+        variables: { description: this.todoDescription },
+        update: (store, { data }) => {
+          let { todos } = store.readQuery({ query: TODOS });
+          todos.push(data.insert_todos.returning[0]);
+          store.writeQuery({
+            query: TODOS,
+            data: { 'Todos': todos }
+          });
+        }
+      });
+      this.todoDescription = '';
+    }
+  }
}
image.png

以本地语言精准翻译,仅提供一个选项:

文本框的value与src/app.js中的todoDescription实现双向绑定,通过input type=”text” value.bind=”todoDescription”实现。这样,输入文本框的内容会同步反映到todoDescription中。

除了bind之外,还有to-view和from-view等等,但是使用bind可以自动判断绑定模式。

因为在 src/app.html 中写了 submit.trigger=”addTodo()”,所以当点击按钮(submit)时将执行 addTodo 方法。
在 addTodo 方法中,将执行一个 Mutation 来注册 todoDescription 的内容。

insertTodo.gif

将Todo标记为已完成。

当您对已完成的待办事项进行勾选时,会将todos表中的done列更新为true。
此外,勾选后会显示删除线。

  <ul>
    <li repeat.for="todo of todos">
-      <input type="checkbox" checked.bind="todo.done">
-      <span>
+      <input type="checkbox" checked.bind="todo.done" change.delegate="updateTodo(todo)">
+      <span css="text-decoration: ${todo.done ? 'line-through' : 'none'}">
        ${todo.description}
      </span>
    </li>
  </ul>
mutation ($uuid: uuid, $done: Boolean) {
  update_todos(where: {uuid: {_eq: $uuid}}, _set: {done: $done}) {
    returning {
      uuid
      done
      description
    }
  }
}
+import UPDATE_TODO from './graphql/UpdateTodo.gql';

export class App {
(略)

+  updateTodo(todo) {
+    client.mutate({
+      mutation: UPDATE_TODO,
+      variables: { uuid: todo.uuid, done: todo.done }
+    });
+  }
}

当todo.done为true时(复选框被选中),text-decoration: line-through样式将应用于其中,并显示删除线。
此外,当复选框的状态发生更改时,将执行updateTodo方法。
在updateTodo方法中,将执行用于更新的mutation,并更新todos表的done列。
(当取消勾选时,将以false值进行更新)

现在我能够将已完成的任务标记为“完成”。

删除Todo


  <input type="checkbox" checked.bind="todo.done" change.delegate="updateTodo(todo)">
  <span css="text-decoration: ${todo.done ? 'line-through' : 'none'}">
    ${todo.description}
  </span>
+  <button click.trigger="removeTodo(todo)">Remove</button>
mutation ($uuid: uuid) {
  delete_todos(where: {uuid: {_eq: $uuid}}) {
    returning {
      description
      done
      uuid
    }
  }
}
+ import REMOVE_TODO from './graphql/RemoveTodo.gql';

export class App {
(略)

+  removeTodo(todo) {
+    client.mutate({
+     mutation: REMOVE_TODO,
+      variables: { uuid: todo.uuid },
+      update: (store, { data }) => {
+        let { todos } = store.readQuery({ query: TODOS });
+        const index = todos.findIndex(t => t.uuid === data.delete_todos.returning[0].uuid);
+        if (index > -1) {
+          todos.splice(index, 1);
+        }
+        store.writeQuery({
+          query: TODOS,
+          data: { 'Todos': todos }
+        });
+      }
+    });
+  }
}

我添加了一个删除按钮,并通过click.trigger在点击时执行removeTodo方法。
removeTodo方法执行了一个用于删除的mutation。

这样,即使错误地注册也可以删除!

总结

大约有一半的内容似乎与Aurelia无关。我通常使用Vue.js的机会很多,因为它的氛围相似,所以很容易理解。正好趁机多尝试一些其他东西。

请看以下:
请用中文原生地改述以上内容,只需要一种选项:
参考

https://github.com/hasura/graphql-engine –> https://github.com/hasura/graphql-engine
https://aurelia.io/ –> https://aurelia.io/

广告
将在 10 秒后关闭
bannerAds