如何将 Kendo UI Grid 绑定到 GraphQL API

什么是 GraphQL

QL = Query Language,即查询语言。

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。这是来自官方(http://graphql.cn/)的说明。

GraphQL的好处是请求你所要的数据。向你的 API 发出一个 GraphQL 请求就能准确获得你想要的数据,不多不少。 GraphQL 查询总是返回可预测的结果。使用 GraphQL 的应用可以工作得又快又稳,因为控制数据的是应用,而不是服务器。

另外只用一个请求就可以获取多个资源。GraphQL 查询不仅能够获得资源的属性,还能沿着资源间引用进一步查询。典型的 REST API 请求多个资源时得载入多个 URL,而 GraphQL 可以通过一次请求就获取你应用所需的所有数据。这样一来,即使是比较慢的移动网络连接下,使用 GraphQL 的应用也能表现得足够迅速。


设置 GraphQL 服务

首先,我们需要创建一个 GraphQL 服务可以接收查询和修改的验证和执行。

1)定义一个类型,描述服务能够进行数据查询:

import {
    GraphQLObjectType,
    GraphQLString,
    GraphQLID,
    GraphQLFloat,
} from 'graphql';
  
module.exports = new GraphQLObjectType({
    name: 'Product',
    fields: () => ({
        ProductID: { type: GraphQLID },
        ProductName: { type: GraphQLString },
        UnitPrice: { type: GraphQLFloat },
        UnitsInStock: { type: GraphQLFloat }
    })
});

2)创建查询用于获取数据和修改服务器端数据:

const RootQuery = new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
        products: {
            type: new GraphQLList(Product),
            resolve(parent, args){
                return products;
            }
        }
    }
});
  
const Mutation = new GraphQLObjectType({
    name: 'Mutation',
    fields: {
        AddProduct: {
            type: Product,
            args:{
                ProductName: { type: new GraphQLNonNull(GraphQLString) },
                UnitPrice: { type: new GraphQLNonNull(GraphQLFloat) },
                UnitsInStock: { type: new GraphQLNonNull(GraphQLFloat) }
            },
            resolve(parent, args) {
                let newProduct = {
                    ProductID: uuidv1(),
                    ProductName: args.ProductName,
                    UnitsInStock: args.UnitsInStock,
                    UnitPrice: args.UnitPrice
                }
                products.unshift(newProduct);
  
                return newProduct;
            }
        },
        UpdateProduct: {
            type: Product,
            args:{
                ProductID: { type: new GraphQLNonNull(GraphQLID) },
                ProductName: { type: new GraphQLNonNull(GraphQLString) },
                UnitPrice: { type: new GraphQLNonNull(GraphQLFloat) },
                UnitsInStock: { type: new GraphQLNonNull(GraphQLFloat) }
            },
            resolve(parent, args) {
                let index = products.findIndex(product => product.ProductID == args.ProductID);
                let product = products[index];
  
                product.ProductName = args.ProductName;
                product.UnitsInStock = args.UnitsInStock;
                product.UnitPrice = args.UnitPrice;
  
                return product;
            }
        },
        DeleteProduct: {
            type: Product,
            args:{
                ProductID: { type: new GraphQLNonNull(GraphQLID) }
            },
            resolve(parent, args) {
                let index = products.findIndex(product => product.ProductID == args.ProductID);
                products.splice(index, 1);
  
                return { ProductID: args.ProductID };
            }
        }
    }
});
  
module.exports = new GraphQLSchema({
    query: RootQuery,
    mutation: Mutation
});

3)通过一个端点经HTTP提供GraphQL服务

import express from 'express';
import cors from 'cors';
import graphqlHTTP from 'express-graphql';
import schema from './schema';
import { createServer } from 'http';
  
const PORT = 3021;
  
var app = express();
  
app.use(cors());
  
app.use('/graphql', graphqlHTTP({
    schema
}));
  
const server = createServer(app);
  
server.listen(PORT, () => {
    console.log(`API Server is now running on http://localhost:${PORT}/graphql`)
});

配置 Grid

为能够使用 Kendo UI Grid ,在客户端引入需要的资源。

<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2018.2.620/styles/kendo.default-v2.min.css" />
<script src="http://kendo.cdn.telerik.com/2018.2.620/js/jquery.min.js"></script>
<script src="http://kendo.cdn.telerik.com/2018.2.620/js/kendo.all.min.js"></script>

作为容器将 Grid 元素增加到页面,然后用 JavaScript 初始化部件。

<div id="content">
  <div id="grid"></div>
</div>
  
$(document).ready(function() {             
  $("#grid").kendoGrid({
    dataSource: dataSource,
    height: 550,
    groupable: true,
    sortable: true,
    pageable: true,
    toolbar: ["create"],
    editable: "inline",
    columns: [{
      field: "ProductID",
      title: "Product ID"
    },
    {
      field: "ProductName",
      title: "Product Name"
    },
    {
      field: "UnitPrice",
      title: "Unit Price"
    },
    {
      field: "UnitsInStock",
      title: "Units in stock"
    },
    {
      command: ["edit", "destroy"],
      title: "Options ",
      width: "250px"
    }]
  });
});
</script>

在客户端使用 GraphQL

数据源提供丰富的配置选项允许您轻松地将其集成到 GraphQL API中,并与网格组件绑定。

编写查询和变更

GraphQL 请求对象上特定的字段,为初始化填充Grid,我们需要对API发出一个查询并返回对象类型。

<script>
  var READ_PRODUCTS_QUERY = "query {" +
    "products { ProductID, ProductName, UnitPrice, UnitsInStock }" +
  "}";
</script>

然后创建更改的adding, updatingdeleting对象类型。

<script>       
  var ADD_PRODUCT_QUERY = "mutation AddProduct($ProductName: String!, $UnitPrice: Float!, $UnitsInStock: Float!){" +
    "AddProduct(ProductName: $ProductName, UnitPrice: $UnitPrice, UnitsInStock: $UnitsInStock ){" +
      "ProductID,"+
      "ProductName,"+
      "UnitPrice,"+
      "UnitsInStock"+
    "}"+
  "}";
  
  var UPDATE_PRODUCT_QUERY = "mutation UpdateProduct($ProductID: ID!, $ProductName: String! ,$UnitPrice: Float!, $UnitsInStock: Float!){" +
    "UpdateProduct(ProductID: $ProductID, ProductName: $ProductName, UnitPrice: $UnitPrice, UnitsInStock: $UnitsInStock){" +
      "ProductID," +
      "ProductName," +
      "UnitPrice," +
      "UnitsInStock" +
    "}" +
  "}";
  
  var DELETE_PRODUCT_QUERY = "mutation DeleteProduct($ProductID: ID!){" +
    "DeleteProduct(ProductID: $ProductID){" +
      "ProductID" +
    "}" +
  "}";
</script>

使用 API

为通过GraphQL查询或更改请求或修改数据,您所要做的就是配置透明读取DataSource的方法。

  • 设置 content-type 为 “application/json.”
  • 设置请求为 “POST.”
  • 通过编写好的 GraphQL 查询/变更作为 “查询” 和模型数据作为 “变量” 参数提交给原厂服务执行CRUD操作,这就是通过 transport.read 的data() 方法。

<script>
    var dataSource = new kendo.data.DataSource({
        pageSize: 20,
        transport: {
            read: {
                contentType: "application/json",
                url: "http://localhost:3021/graphql",
                type: "POST",
                data: function() {
                    return { query: READ_PRODUCTS_QUERY };
                }
            },
            update: {
                contentType: "application/json",
                url: "http://localhost:3021/graphql",
                type: "POST",
                data: function(model) {
                    return {
                        query: UPDATE_PRODUCT_QUERY,
                        variables: model
                    };
                }
            },
            destroy: {
                contentType: "application/json",
                url: "http://localhost:3021/graphql",
                type: "POST",
                data: function(model) {
                    return {
                        query: DELETE_PRODUCT_QUERY,
                        variables: model
                    };
                }
            },
            create: {
                contentType: "application/json",
                url: "http://localhost:3021/graphql",
                type: "POST",
                data: function(model) {
                    return {
                        query: ADD_PRODUCT_QUERY,
                        variables: model
                    };
                }
            },
            parameterMap: function(options, operation) {
                return  kendo.stringify(options);
            }
        },
        schema: {
            data: function(response) {
                var data = response.data;
  
                if(data.products) {
                    return data.products;
                } else if(data.AddProduct) {
                    return data.AddProduct;
                } else if(data.UpdateProduct) {
                    return data.UpdateProduct;
                } else if(data.DeleteProduct){
                    return data.DeleteProduct;
                }
            },
            model: {
                id: "ProductID",
                fields: {
                    ProductID: { type: "string", editable: false },
                    ProductName: { type: "string" },
                    UnitPrice: { type: "number" },
                    UnitsInStock: { type: "number" }
                }
            }
        }
    });
</script>

格式化请求参数和解析返回的数据

除了配置传输外,数据源的其他特性,如参数映射parameterMap()和模式选项,对于编码请求参数和解析API响应都是有用的:

  • 当将数据发送到GraphQL API时,CRUD操作的请求参数需要用JSON格式进行编码,为了帮助这一点,必须使用DataSource的transport.parameterMap() 选项。
  • 另一方面,当从服务接收数据时,DataSource的schema.data() 方法来解析接收到的响应,以便Grid可以确定响应的哪个数据字段。

开始浏览 Grid

在这里,通过简单地设置数据源,我们得到了采用 GraphQL API 的可运行的 Grid 例子。从这里开始,您可以开始探索Grid的大量功能,也可体验其他70多个可用的 Kendo UI 组件,并很容易地将它们绑定到GraphQL 服务。