从 Oracle JET 虚拟 DOM 应用程序中的 REST API 提取数据

简介

此教程演示如何访问 REST 服务,将其集成到 Oracle JavaScript Extension Toolkit (Oracle JET) 虚拟 DOM 应用程序中,以及如何将数据绑定到用户界面中的列表视图。

目标

在本教程中,您将学习如何创建 RESTDataProvider 类的实例。此类表示基于 JSON 的 REST 服务可用的数据。

Prerequisites

任务 1:下载 Starter Virtual DOM 应用程序

如果您继续在上一学习路径中创建的应用程序中工作,请跳过此任务。

  1. jet-virtual-dom-app-temp.zip 重命名为 JET-Virtual-DOM-app.zip。将内容提取到 JET-Virtual-DOM-app 目录。

  2. 导航到 JET-Virtual-DOM-app 目录并恢复 Oracle JET 虚拟 DOM 应用程序。

    npm install
    

    虚拟 DOM 应用程序已就绪可供使用。

任务 2:访问 REST 服务

单击 Apex 链接可查看活动资源端点的 REST 数据。

数据包含具有各种属性的作业的列表。

{
  "items": [
      {
      "id": 1,
      "name": "Baseball",
      "short_desc": "Equipment we carry for baseball players.",
      "image": "css/images/product_images/baseball.jpg"
      },
   . . .
   ],
   "hasMore": false,
   "limit": 25,
   "offset": 0,
   "count": 4,
   "links": [
      {
      "rel": "self",
      "href": "https://apex.oracle.com/pls/apex/oraclejet/lp/activities/"
      },
      . . .
   ]
}

熟悉端点返回的数据和属性。在本教程的后面部分中,创建 RESTDataProvider 实例时,您需要了解这些详细信息。请注意,例如,端点如何返回引用一系列单个活动的 items 属性。

任务 3:创建数据提供程序以提取活动数据

  1. 导航到 JET-Virtual-DOM-app/src/components/ 目录并在编辑器中打开 ParentContainer1.tsx 文件。

  2. ParentContainer1.tsx 文件的开头,导入 RESTDataProvider 模块,并删除 MutableArrayDataProvider 模块和 store_data.json 文件的 import 语句或注释掉这些语句。

    我们还会导入在以后创建 RESTDataProvider 时要使用的 useMemo 钩子。

    import { h } from "preact";
    . . .
    // import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider");
    // import * as storeData from "text!./store_data.json";
    import { RESTDataProvider } from "ojs/ojrestdataprovider";
    import { useState, useMemo } from "preact/hooks";
    . . .
    
  3. 创建一个 keyattributes 变量和一个 restServerURLActivities 变量,该变量引用活动键属性和 REST 端点,您将该端点传递到下一步将创建的 RESTDataProvider 实例。

    let keyAttributes: string = 'id';
    // REST endpoint that returns Activity data
    const restServerURLActivities: string =
      'https://apex.oracle.com/pls/apex/oraclejet/lp/activities/';
    
  4. 创建一个引用 RESTDataProvider 模块的新 activityDataProvider 变量,并删除或注释掉引用 MutableArrayDataProvider 模块的现有 activityDataProvider 变量。

    我们在 ParentContainer1 函数中创建新的 activityDataProvider 变量并将其包装在 useMemo 挂钩中,以确保仅当数据提供程序中的数据实际更改时才重新创建数据提供程序实例。

    const ParentContainer1 = () => {
    
    const activityDataProvider = useMemo(() => new RESTDataProvider<Activity["id"], Activity>({
       keyAttributes: keyAttributes,
       url: restServerURLActivities,
       transforms: {
          fetchFirst: {
          request: async (options) => {
             const url = new URL(options.url);
             const { size, offset } = options.fetchParameters;
             url.searchParams.set("limit", String(size));
             url.searchParams.set("offset", String(offset));
             return new Request(url.href);
          },
          response: async ({ body }) => {
             const { items, totalSize, hasMore } = body;
             return { data: items, totalSize, hasMore };
          },
          },
       },
     }), [])
    . . .
    

    注:上面从端点响应主体提取数据和其他属性的 response 函数必须返回具有 data 属性的对象。鉴于我们使用的端点返回一个 items 属性,我们将在响应函数中将此后一个属性分配给 data

  5. 保存 ParentContainer1.tsx 文件。

    您的 ParentContainer1.tsx 文件应类似于 ParentContainer1-a.tsx.txt

  6. 导航到 JET-Virtual-DOM-app/src/components/Activity 目录并在编辑器中打开 ActivityContainer.tsx 文件。

  7. ActivityContainer.tsx 文件的开头,导入 RESTDataProvider 模块并注释掉或删除 MutableArrayDataProvider 模块的 import 语句。

       import { h, ComponentProps } from "preact";
       . . .
       // import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider");
       import { RESTDataProvider } from "ojs/ojrestdataprovider";
       . . .
    
    
  8. Props 类型别名中,修改可选的 data 属性以引用 RESTDataProvider 类型,而不是先前存在的类型 MutableArrayDataProvider<Activity["id"], Activity>

       type Props = {
          data?: RESTDataProvider<Activity["id"], Activity>;
          // data?: MutableArrayDataProvider<Activity["id"], Activity>;
       . . .
       };
    
  9. 保存 ActivityContainer.tsx 文件。

    您的 ActivityContainer.tsx 文件应类似于 ActivityContainer.tsx.txt

任务 4:添加错误处理程序以管理提取数据失败

RESTDataProvider 实例提供了一个错误选项,当尝试提取数据失败时,可以使用该选项调用回调函数。您将在尝试提取活动列表失败的情景中实施此功能。

  1. JET-Virtual-DOM-app/src/components/ 目录中的 ParentContainer1.tsx 文件中,从 Preact 导入 useRef 挂钩。

    . . .
    import { RESTDataProvider } from "ojs/ojrestdataprovider";
    import { useState, useMemo, useRef } from "preact/hooks";
    . . .
    
  2. 在引用 RESTDataProvideractivityDataProvider 变量中,添加 error 选项及其调用的回调函数 (fetchErrorHandler) 的引用。

    const ParentContainer1 = () => {
    
       const activityDataProvider = useMemo(() => new RESTDataProvider<Activity["id"], Activity>({
          keyAttributes: keyAttributes,
          url: restServerURLActivities,
          error: fetchErrorHandler,
          transforms: {
          . . .
    
  3. activityDataProvider 变量之前,添加 fetchErrorHandler 的代码以及用于确定是否成功提取数据的钩子(useStateuseRef)。

    . . .
    const ParentContainer1 = () => {
    
    const [fetchStatus, setFetchStatus] = useState(true);
    const fetchError = useRef<string>();
    
    const fetchErrorHandler = (errorDetail: RESTDataProvider.FetchErrorDetail<number, Activity> |
                                            RESTDataProvider.FetchResponseErrorDetail<number, Activity>) => {
       setFetchStatus(false);
       if (errorDetail.hasOwnProperty('response')) {
          fetchError.current = `${(errorDetail as RESTDataProvider.FetchResponseErrorDetail<number, Activity>).response.status}`;
       }
       else {
          fetchError.current = (errorDetail as RESTDataProvider.FetchErrorDetail<number, Activity>).error.message;
       }
    }
    
    const activityDataProvider = new RESTDataProvider<Activity["id"], Activity>({
    . . .
    
  4. ParentContainer1.tsx 文件结尾的 return 语句中,添加一个检查,确定在尝试提取数据失败时是显示活动列表还是显示消息。

    . . .
    return (
       <div>
          {fetchStatus ? (
          <div id="parentContainer1" class="oj-flex oj-flex-init">
             <ActivityContainer data={activityDataProvider} onActivityChanged={activityChangedHandler} />
             {showActivityItems() && (<ParentContainer2 activity={selectedActivity} />)}
             {!showActivityItems() && (<h4 class="oj-typography-subheading-sm">Select activity to view items</h4>)}
          </div>) :
          (<p>Sorry that we couldn't get your product information right now. Please contact your system administrator.</p>
          )}
       </div>
    );
    };
    
    export default ParentContainer1;
    
  5. 保存 ParentContainer1.tsx 文件。

    您的 ParentContainer1.tsx 文件应类似于 ParentContainer1-b.tsx.txt

任务 5:创建数据提供程序以提取项数据

使用另一个 RESTDataProvider 实例提取数据子集,即特定活动的项列表。您可以通过提供包含所选活动 ID 的新 URL 来执行此操作。

  1. 导航到 JET-Virtual-DOM-app/src/components/ 目录并在编辑器中打开 ParentContainer2.tsx 文件。

  2. ParentContainer2.tsx 文件的开头,导入 RESTDataProvider 模块,并删除 MutableArrayDataProvider 模块和 store_data.json 文件的 import 语句或注释掉这些语句。还要导入在创建的 RESTDataProvider 实例中启用过滤功能时将使用的 TextFilter 接口。

    import { h } from "preact";
    . . .
    import { RESTDataProvider } from "ojs/ojrestdataprovider";
    import { TextFilter } from "ojs/ojdataprovider";
    
    // import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider");
    // import * as storeData from "text!./store_data.json";
    . . .
    
    
  3. Item 类型别名之后,创建一个 baseServiceUrl 变量以引用将传递给 RESTDataProvider 实例的 REST 端点,该端点将在下一步中创建。

    type Item = {
       . . .
     };
    
    const baseServiceUrl =
      "https://apex.oracle.com/pls/apex/oraclejet/lp/activities/";
    
  4. 创建 RESTDataProvider 的初始实例,在后续步骤中将该实例传递给 useStateuseEffect Preact 钩子。

       const baseServiceUrl = 'https://apex.oracle.com/pls/apex/oraclejet/lp/activities/';
    
       let INIT_DATAPROVIDER = new RESTDataProvider<ActivityItem['id'], ActivityItem>({
       keyAttributes: 'id',
       url: baseServiceUrl,
       transforms: {
          fetchFirst: {
             request: null!,
             response: (): any => {
             return { data: [] };
             },
          },
       },
       });
    
  5. 注释掉或删除预先存在的代码,这些代码创建了一个变量以从 store_data.json 文件读取数据并创建了 MutableArrayDataProvider 的初始实例。

       // const activityData = JSON.parse(storeData);
       // let activityItemsArray = activityData[0].items;
    
       // // Create data provider instance for the array of activity items for the selected activity
       // const INIT_DATAPROVIDER = new MutableArrayDataProvider<ActivityItem["id"], ActivityItem>(activityItemsArray, {
       //   keyAttributes: "id",
       // })
    
  6. ParentContainer2 函数中,将管理 MutableArrayDataProvider 实例的现有 useEffect 挂钩替换为新定义,该定义将为与所选活动 ID 对应的活动项创建 RESTDataProvider。此新定义还包括要在活动项 name 字段上筛选的文本筛选器。

    const ParentContainer2 = (props: Props) => {
    . . .
    useEffect(() => {
       setactivityItemDP(
          new RESTDataProvider<ActivityItem["id"], ActivityItem>({
          keyAttributes: "id",
          capabilities: {
             filter: {
                textFilter: true,
             },
          },
          url: baseServiceUrl + "/" + props.activity?.id + "/items/",
          textFilterAttributes: ["name"],
          transforms: {
             fetchFirst: {
                request: async (options) => {
                const url = new URL(options.url);
                const { size, offset } = options.fetchParameters;
                url.searchParams.set("limit", String(size));
                url.searchParams.set("offset", String(offset));
                const filterCriterion = options.fetchParameters
                   .filterCriterion as TextFilter<Item>;
                const { textFilterAttributes } = options.fetchOptions;
                if (
                   filterCriterion &&
                   filterCriterion.text &&
                   textFilterAttributes
                ) {
                   const { text } = filterCriterion;
                   textFilterAttributes.forEach((attribute) => {
                      url.searchParams.set(attribute, text);
                   });
                }
    
                return new Request(url.href);
                },
                response: async ({ body }) => {
                const { items, totalSize, hasMore } = body;
                return { data: items, totalSize, hasMore };
                },
             },
          },
          })
       );
    }, [props.activity]);
    
    return (
    . . .
    
  7. 保存 ParentContainer2.tsx 文件。

    您的 ParentContainer2.tsx 文件应类似于 ParentContainer2.tsx.txt

  8. 导航到 JET-Virtual-DOM-app/src/components/ActivityItem 目录并在编辑器中打开 ActivityItemContainer.tsx 文件。

  9. ActivityItemContainer.tsx 文件的开头,导入 RESTDataProvider 模块并添加注释或删除 MutableArrayDataProvider 模块的 import 语句。

    import { h, ComponentProps } from "preact";
    . . .
    // import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider");
    import { RESTDataProvider } from "ojs/ojrestdataprovider";
    . . .
    
  10. Props 类型别名中,修改 data 属性以引用 RESTDataProvider 类型,而不是先前存在的类型 MutableArrayDataProvider<Activity["id"], Activity>

    type Props = {
      // data?: MutableArrayDataProvider<ActivityItem["id"], ActivityItem>;
      data?: RESTDataProvider<ActivityItem['id'], ActivityItem>;
      selectedActivity: Item | null;
      onItemChanged: (item: Item) => void;
    };
    
  11. 保存 ActivityItemContainer.tsx 文件。

    您的 ActivityItemContainer.tsx 文件应类似于 ActivityItemContainer.tsx.txt

任务 6:测试虚拟 DOM 应用程序

  1. 在终端窗口中,转到 JET-Virtual-DOM-app 目录并运行虚拟 DOM 应用程序。

    npx ojet serve
    
  2. 在浏览器窗口中,查看虚拟 DOM 应用程序中的动态更改。

    “提取的记录”屏幕

  3. 关闭显示正在运行的虚拟 DOM 应用程序的浏览器窗口或选项卡。

  4. 在终端窗口中,按 Ctrl+C,如果出现提示,输入 y 以退出 Oracle JET 工具批处理作业。

  5. 在终端窗口中,使用以下附加命令行参数运行虚拟 DOM 应用程序。

    npx ojet serve --server-port=8144 --livereload-port=8145
    

    在此情况下,虚拟 DOM 应用程序显示以下消息,因为其尝试访问的 REST 服务仅接受 ojet serve 命令在默认情况下使用的服务器端口 (8000) 上的请求,因此 RESTDataProvider 尝试从 REST 服务提取失败。

    Sorry that we couldn't get your product information right now. Please contact your system administrator.
    

后续步骤

继续本模块中的下一个教程。

本教程是 CRUD Operations Using a REST Service 模块的一部分。

您可以返回到虚拟 DOM 学习路径的主页,以访问有关构建虚拟 DOM 应用程序的所有模块。

更多学习资源

通过 docs.oracle.com/learn 浏览其他实验室,或者通过 Oracle Learning YouTube 频道访问更多免费学习内容。此外,请访问 education.oracle.com/learning-explorer 以成为 Oracle Learning Explorer。

有关产品文档,请访问 Oracle 帮助中心