数据源

应用制作工具微件和客户端 API 通过数据源访问数据。数据源定义了以下两个方面:

  • 您的应用如何查询模型中的数据
  • 您的界面如何访问此信息

数据源具有一个指向数据源中选定项的 item 属性。在您的界面中,微件使用 item 属性显示或修改有关数据源中当前选定记录的信息。客户端脚本还可以通过此属性访问和修改记录。

在本文中,您将了解以下内容:

  1. 数据源的类型
  2. 数据源生命周期
  3. 如何创建查询数据源,包括如何构建查询和配置其他数据源设置
  4. 如何使用客户端脚本优化查询数据源
  5. 如何使用关系数据源访问关联的记录
  6. 如何使用创建模式数据源从用户输入创建草稿记录

数据源的类型

应用制作工具有三种类型的数据源:

  1. 查询数据源
  2. 关系数据源
  3. 创建模式数据源

查询数据源存储以下内容:

  • 有关查询单个模型的信息
  • 上次执行查询的记录

默认情况下,系统会为每个模型创建一个查询数据源,其名称与模型的名称相同。您可以自定义查询数据源并为某个模型创建多个查询数据源。如果要为单个模型选择不同的选项(例如过滤结果、不同的排序顺序或不同的页面大小),则您可能需要多个查询数据源。如果您希望同时选择不同的记录,例如比较两个 datasource.item 值,也可以使用多个数据源。您可以使用界面微件和客户端脚本更改并重新执行查询。上次查询的结果可以绑定到微件属性,也可以在客户端脚本中访问这些结果。

您在开发界面时,通常会将查询数据源与页面的顶层面板或复合微件的顶层面板相关联。这样,该面板的所有子微件都会自动继承该数据源,同时您可以将微件属性与数据源记录中的字段相关联。

通过关系数据源,您可以显示有关关联记录的数据。您可以从查询数据源或其他关系数据源为特定关系端创建关系数据源。关系数据源会显示与父数据源当前选定记录相关联的关系端中的记录。

关系数据源的常见用法是实现主实例详细信息视图。例如,您可以构建界面以显示客户订单及其相关项。您有订单这两个模型,并在它们之间创建一对多关系。如需了解有关如何在界面编辑器中使用关系数据源的更多信息,请转到将数据绑定到您的界面

创建模式数据源会在父查询或关系数据源中创建项。

数据源生命周期

数据源可以处于以下四种状态之一:

  1. 已卸载
  2. 已卸载 + 正在加载
  3. 已加载
  4. 已加载 + 正在加载

已卸载的数据源没有任何数据;该数据源的项是空数组,并且当前选定的项为 null。正在加载的数据源正在获取数据。已加载的数据源有数据,但数据可能为空。

如需使数据源处于正在加载状态之一,必须触发加载操作。可以通过以下方式触发加载操作:

  1. 默认情况下,当使用该数据源的微件附加到页面时,系统会触发加载操作。通常,当用户打开微件的父页面时,系统会附加微件。
  2. 默认情况下,附加绑定到数据源的数据属性的微件时,系统会触发加载操作。例如,如果订单页面中有一个绑定到客户模型中某个字段的标签微件,则系统会在显示“订单”页面时加载客户数据源。
  3. 数据源上调用 loadloadPageprevPagenextPage 方法的客户端脚本会触发加载操作。

您可以对查询数据源停用自动加载数据,这样可以防止出现以上列表中前两种触发加载操作的情况。如需卸载某个数据源,请在该数据源上调用 unload 方法。

查询数据源具有 onLoad 事件,系统会在某个数据源转换为已加载状态时触发该事件。在您希望运行在数据源加载数据后执行操作的脚本时,此 onLoad 事件非常有用。例如,您可以在 onLoad 代码编辑器中输入以下脚本,以便在数据可用后显示微件:

var widget = app.pages.MyPage.descendants.MyWidget;
    widget.visible = true;
    

查询数据源

创建模型时,系统会自动创建查询数据源。您可以自定义查询数据源并为模型创建多个查询数据源。

如需修改或创建查询数据源,请执行以下操作:

  1. 点击导航窗格中的模型,然后转到其 Datasources 标签页。
  2. 如需修改某个数据源,请点击该数据源。如需创建数据源,请点击添加数据源
  3. 配置数据源。系统会自动保存更改。

数据源具有以下配置选项:

查询服务器脚本

默认情况下,数据源会加载模型中的所有记录。通过查询脚本属性,您可以在指定自定义服务器端逻辑(例如过滤条件)时替换默认查询行为。对于计算模型而言,通过查询脚本,您可以完整定义某个数据源的查询行为以及在该数据源中创建记录。

查询脚本功能和要求:

  • 您可以从查询脚本编辑器调用服务器脚本函数,这在查询脚本很长时非常有用。
  • 您可以使用自定义参数将参数从客户端传递到查询脚本。
  • 查询脚本必须返回一组属于数据源模型的记录。

过滤条件示例

例如,默认的员工数据源查询操作会返回员工模型中的所有记录。假设您只想返回年龄为偶数且大于查询中指定的最小年龄的员工。如需将此查询应用到数据源,请在查询脚本代码编辑器中输入以下代码:

var employeeResult = [];
    // Modify passed query to also filter by minimum age.
    query.filters.Age._greaterThan = 20;
    var employeesAboveMinimumAge = query.run();
    for (var i = 0; i < employeesAboveMinimumAge.length; i++) {
      if (employeesAboveMinimumAge[i].age % 2 == 0) {
        employeeResult.push(employeesAboveMinimumAge[i]);
      }
    }
    return employeeResult;
    

创建计算记录的示例

计算模型用于将脚本结果表示为记录,以便您可以在用户界面中使用它们。例如,您希望查询您的员工模型,以创建一个计算模型,生成每个位置上员工人数的记录。由于数据库中不存在实际的 EmployeesByLocation 记录,因此查询脚本需要调用 newRecord() 来创建记录。以下脚本计算每个地点的员工数,并在计算模型中为每个地点创建一条记录:

var calculatedModelRecords = [];
    var recordsByLocation = {};
    var allEmployees = app.models.Employees.newQuery().run();
    for (var i = 0; i < allEmployees.length; i++) {
      var employee = allEmployees[i];
      if (!recordsByLocation[employee.location]) {
        var calculatedModelRecord = app.models.EmployeesByLocation.newRecord();
        calculatedModelRecord.numberOfEmployees = 1;
        calculatedModelRecord.location = employee.location;
        calculatedModelRecords.push(calculatedModelRecord);
        recordsByLocation[employee.location] = calculatedModelRecord;
      } else {
        recordsByLocation[employee.location].numberOfEmployees++;
      }
    }
    return calculatedModelRecords;
    

自定义参数

查询脚本还可以使用该数据库无法提供的参数。例如,您可能想根据您从 Web 服务获取的某家公司的当前股票价格来过滤数据。您可以使用自定义参数将参数从客户端传递到服务器上的查询脚本。

如需添加参数,请执行以下操作:

  1. Datasources 标签页,点击 Add Parameter。
  2. 选择参数的类型。
  3. 输入参数的名称。

访问并绑定到数据源查询的 parameters 属性下的参数。例如,您可以使用绑定语句将文本字段微件的值属性绑定到查询参数:

textField.value <-> datasource.query.parameters.MyParam
    

查询脚本可以将该参数作为查询参数进行访问。如需将此方法应用到上一个示例,我们可以将以下代码添加到查询脚本中。添加以后,只有在名为 RestrictAge 的布尔值自定义参数为 true 时,系统才会添加最小年龄过滤条件:

// Modify query to also filter by minimum age if client enables the option.
    if (query.parameters.RestrictAge) {
      query.filters.Age._greaterThan = 20;
    }
    var employees = query.run();
    

查询构建器

查询构建器可用于 Cloud SQL 数据源。它是对 MySQL WHERE 子句的简化模拟,该子句支持对相关模型进行过滤,并且使得针对 null 的查询更容易执行。您可以使用查询构建器根据应用用户提交的信息运行复杂的查询。

例如,您可以构建一个应用,用户可以通过该应用在 HR 数据库中搜索符合不同退休计划资格的员工。查询构建器有助于创建应用在查找数据时所需的逻辑表达式。这样,您就不必编写自定义脚本来解释用户输入并将其应用到 Cloud SQL 数据库搜索。查询构建器还支持代码自动填充(按 Ctrl + 空格键)。

如需使用用户输入参数实现查询,请执行以下操作:

  1. 在查询构建器中构建查询表达式,并同时创建用户输入参数。
  2. 将微件属性绑定到用户输入参数。

构建查询

查询构建器会根据以下规则处理查询:

  • 查询构建器按照从左到右的顺序处理查询表达式。
  • 查询构建器通过在用户输入的参数之前添加一个英文冒号 (:),使该参数与模型字段相区别。
  • and 运算符优先于 or 运算符。
  • 英文括号内的子查询表达式优先于 and 和 or 运算符。
  • “!”运算符会对表达式的布尔值执行求反运算。
  • 叶运算符的“?”修饰符会将表达式右侧的 null 值视为 true。您无法对使用“?”修饰符的表达式执行求反运算。

对于 HR 数据库示例,应用需要返回给定年龄范围内的员工或者在指定日期之后入职的员工。查询如下:

(Age >= :AgeMin and Age < :AgeMax) or StartDate > :StartDate
    
  • Age - 应用模型中的一个字段。该字段存储每个员工的年龄。应用要求用户输入最小年龄 (:AgeMin) 和最大年龄 (:AgeMax)。该查询会返回年龄在上述范围内的员工。
  • 英文冒号 (:) - 英文冒号用于标识通过应用界面获取其值的参数。
  • Parentheses - 用于标识某个子查询。应用制作工具会在处理 :StartDate 之前先处理该子查询。
  • and - 运算符。该运算符指示应用返回同时符合其前后两个条件的员工。
  • or - 运算符。该运算符指示应用返回符合其前后两个条件中任意一个条件的员工。
  • StartDate - 一个用于存储员工入职日期的字段。用户输入 :StartDate 以后,应用会返回在该日期之后入职的员工。

将查询表达式绑定到界面

当您将参数(前面带有英文冒号)添加到表达式时,查询构建器会自动识别这些参数并在查询构建器下面的区域中创建查询参数。这些参数也会显示在数据绑定界面中,您可以使用该界面将这些参数绑定到您应用中的微件。

如需使用 HR 数据库示例,请执行以下步骤:

  1. 打开微件 并将两个文本框 添加到某个页面。
  2. 将文本框的 value 属性绑定到以下代码:
    • @datasource.query.parameters.AgeMin
    • @datasource.query.parameters.AgeMax
  3. 将日期框微件 添加到该页面。
  4. 将日期框微件的 value 属性绑定到以下代码:
    • @datasource.query.parameters.StartDate
  5. 将 onValueChange 事件更改为为输入微件重新加载数据源

更多查询构建器示例

查询 返回
Age >= :AgeMin and Age <= :AgeMax and Active = :IsActive 年龄在最小年龄和最大年龄之间的所有在职员工。
(Status = :Pending or Status = :Active) and Name startsWith :NamePrefix 姓名以 NamePrefix 开头的所有停薪留职员工或在职员工。
!(Status = :Pending or Status = :Active) 既没有停薪留职也没有在职的所有员工。
Age >=? :AgeMin

如果查询参数 AgeMin 为 null,则系统会返回所有员工。否则,系统会返回年龄超过最小年龄的员工。

在界面中绑定查询参数 AgeMin 时,此运算符很有用。例如,如果 AgeMin 查询参数绑定到某个文本字段,则系统最初会返回所有员工。一旦用户输入 AgeMin 的值,系统就会过滤记录。如果用户清除该文本字段,则系统会再次显示所有员工。

如果查询使用 >= 运算符而不是 >=?,则系统最初不会返回任何员工,或者,当用户清除该文本字段时,系统也不会返回任何员工。

Role notIn :Engineering or :Marketing

所有未担任工程或市场营销职位的员工。

innotIn 与多选微件 配合良好,用户可以通过该微件从您定义的若干个参数中选择多个参数。

Computer startsWith :Chromebook and notContains :Pixel 拥有 Chromebook(无论具体型号如何)的所有员工,不包括拥有 Chromebook Pixel 的员工。

适用于 SQL 计算模型的 SQL 查询

如需了解详情,请转到 SQL 计算模型

其他查询数据源属性

页面大小

页面大小设置控制数据源为单一查询返回的记录数。使用此设置可以控制界面中返回的结果数并提高性能。如果服务器有 10000 条记录,则系统在界面中一次性加载所有记录的速度会很慢并且无法管理所有记录。对于这种情况,可以改用以下方法:设置页面大小并创建一个在数据源上调用 nextPage 方法的 Next 按钮。

如需显示所有记录,请将页面大小设置为零。

页面大小是数据源的属性,因此您可以执行以下操作:

  • 将微件属性绑定到页面大小
  • 访问和修改客户端脚本中的页面大小

例如,通过将滑块微件的 value 属性绑定到数据源的 pageSize 属性,您可以让用户控制显示的记录数。

排序

您可以按任何可排序的字段以升序或降序对记录进行排序。如需按多个字段排序,请使用客户端查询脚本

手动保存模式

默认情况下,在应用制作工具中更改数据源项的值时,更改会立即自动保存到服务器。例如,如果用户更改“文本字段”微件中的值,则新值会自动保存到它所绑定到的记录字段。

如需停用此自动保存行为,您可以将数据源设置为手动保存模式。具体的操作方法是:在模型编辑器中的数据源标签上,选中手动保存模式复选框。

在手动保存模式下,界面或您的脚本必须明确提示用户保存更改。在界面中,您的输入字段和表单的行为类似于传统的 Web 表单,并且需要用户执行相关操作。如需保存更改,用户必须点击保存按钮,该按钮的 onClick 操作是将更改保存到数据源

在客户端脚本中,您可以使用以下方法管理更改:

  • saveChanges - 将当前更改保存到服务器。
  • clearChanges - 放弃在上次调用 saveChanges 以后所做的任何更改。
  • hasChanges - 如果数据源有未保存的更改,则返回 true

对通过数据源的 itemitems 属性访问的记录所做的更改(包括通过关系属性访问的记录)会在客户端上进行本地存储,并且可以保存或回滚。

自动加载数据

默认情况下,只要微件绑定到数据源上的数据,数据源就会自动加载其数据。如果取消选中 Automatically load data 复选框,则数据源只有在您调用 loadloadPagenextPageprevPage 方法时才会加载数据。

如果您希望用户在系统加载数据之前提供查询过滤条件的值,请延迟加载数据。

onLoad 操作

如需在数据源加载时运行客户端 JavaScript,请在代码框中输入脚本。系统每次从服务器向客户端返回查询结果时,都会触发 onLoad 操作。请在数据源生命周期部分中查找有关加载的更多信息。

针对项更改的操作

如需在当前项更改时运行客户端 JavaScript,请在代码框中输入脚本。

由于以下原因,当前项可能会更改:

  • 数据源重新加载其数据,并且当前项不在返回的数据中。
  • 客户端脚本调用数据源上的 nextprev 方法。
  • 客户端脚本或绑定将某个值赋予数据源上的 item 属性。
  • 客户端脚本调用数据源上的 selectIndexselectKey 方法。
  • 某些微件会自动更改数据源的当前项。例如,只要用户点击一行,列表微件就会更改其数据源的当前项。

预取

当您的客户端脚本或界面使用关联记录时,系统会使用预取从关系端高效地加载这些关联记录。如果您的客户端脚本使用关系中的记录,则您必须从服务器显式加载关联记录,因为其他关系端中的关联记录不会自动加载。加载关联记录的一种方式是启用预取,预取会指示应用制作工具在加载查询结果时加载相关记录。

如需启用预取,请执行如下操作:

  1. 点击导航窗格中的模型,然后转到其 Datasources 标签页。
  2. 点击数据源。
  3. 找到 Prefetch,然后点击文本字段。此操作会打开与数据源有关系的模型菜单。
  4. 选择您要为其预取数据的模型,然后点击 Add prefetch。如果您需要预取数据以获得更多关系,请添加更多模型。

    如果您有一系列关系,则可能需要预取与模型有关系的模型数据。点击 Advanced 并使用树来选择要预取的关联模型。

如需移除预取,请指向相应行,并点击“Delete”

如果数据源有许多记录并且您需要为所有记录加载关联记录,则预取可以提高界面性能。例如,您有一个员工模型,该模型自身具有经理关系,该关系有经理团队成员这两个关系端。您的界面会根据查询数据源显示员工记录列表,并且会显示每个员工的经理

如果没有预取,则应用制作工具会为数据源中的每条员工记录请求关联经理记录。此过程可能会很慢,因为系统需要向服务器发送许多请求。如果启用预取,则应用制作工具会同时加载关联记录和查询结果,并且在完成初始加载操作以后界面的工作速度会变得更快。

请在使用客户端脚本修改关联中了解将关联记录加载到客户端的其他方式。

优化客户端上的数据源查询

查询数据源包含一些属性和方法,您可以将这些属性和方法与客户端脚本搭配使用以修改查询。查询对象表示系统在加载数据源时发送到服务器的查询。它包含一些属性,这些属性指定查询条件以及要返回的结果页面。

在查询数据源上触发加载操作时,系统会出现以下情况:

  1. 查询数据源会将其当前查询发送到服务器。
  2. 查询数据源会使用查询结果更新其数据。
  3. 客户端上的记录也会使用查询结果进行更新。

加载查询数据源时,即使查询结果中的记录未更改,您也可能会在客户端上看到记录值发生了更改。如果服务器端脚本或其他客户端更改了记录,就会出现这种情况。

过滤条件

查询对象的 filters 属性保存模型字段的查询条件。请使用以下语法指定过滤条件:

datasource.query.filters.field-id.filter-operator = value

其中:

  • field-id 是模型中可用于过滤的字段。
  • filter-operator 是过滤操作,例如 equalscontains。系统支持哪些过滤条件运算符取决于字段类型和数据库。请在客户端 API 查询文档中详细了解过滤条件运算符。
  • value 是用于查询中的字段和运算符的参数。该值的类型必须与字段的类型相匹配,但 innotIn 运算符除外。对于 innotIn 运算符,参数值必须是字段类型的数组。

对于列表字段,列表中的一个或多个项必须满足查询过滤条件。如果将多个值赋予 filters 下的属性,则记录必须与要加载的所有过滤条件相匹配。

Cloud SQL 模型数据源的查询对象还包含关系过滤条件属性。通过这些属性,您可以按关联记录、其字段或键来过滤记录。系统仅支持一对一和多对一关系的过滤。

客户端查询示例:

  • 单个过滤条件 - 以下代码将一个查询过滤条件添加到微件数据源,该数据源返回姓名为“John Smith”的记录:

    var datasource = widget.datasource;
        datasource.query.filters.Name._equals = 'John Smith';

  • 多个过滤条件 - 以下代码添加多个查询过滤条件,这些过滤条件匹配姓名以“John”开头且年龄超过 18 岁的记录。

    var datasource = widget.datasource;
        datasource.query.filters.Name._startsWith = 'John';
        datasource.query.filters.Age._greaterThan = 18;

  • 列表字段匹配 - 以下查询加载记录 record.Emails = ['smith.j@example.com', 'john@example.com'],因为 Emails 列表字段中的项之一以 john@ 开头:

    datasource.query.filters.Emails._startsWith = 'john@';

  • in 运算符 - 以下代码在查询过滤条件中使用 in 运算符加载 favoriteColor 字段值为 blue 或 green 的记录:

    var datasource = widget.datasource;
        var colors = ['blue', 'green'];
        datasource.query.filters.FavoriteColor._in = colors;

  • 关系字段过滤条件 - 如果员工模型与自身具有一对多的关系,该关系表示经理及其团队成员,则您可以执行以下操作:

    • 加载分配给 manager1 的团队成员的记录:

      var datasource = widget.datasource;
          datasource.query.filters.Manager._equals = 'manager1';

    • 按经理的记录键过滤:

      datasource.query.filters.Manager._key._in = ['manager1RecordKey', 'manager2RecordKey'];

    • 按经理的经理姓名过滤:

      datasource.query.filters.Manager.Manager.Name._equals = 'John Doe';

    与其他字段过滤条件一样,如果您将关系过滤条件属性值设置为 null,则系统不会应用过滤条件。

排序

您可以替换客户端脚本中为数据源配置的排序。您可以按数据源模型的一个或多个可排序的字段对数据进行排序。对于 Cloud SQL 模型数据源,只要关系端是一对一或多对一关系,您还可以按相关记录中的字段进行排序。例如,如果您有多对一的城市 - 省/自治区/直辖市关系,则可以按省/自治区/直辖市对城市进行排序。

请详细了解查询排序属性

页面索引

页面索引属性指定要加载的页码。默认情况下,系统会加载查询结果的第 1 页。您可以将此属性更改为任何正数;系统下次加载查询数据源时,会加载该页码的记录。

页面大小

页面大小属性指定要从服务器返回查询结果的记录数。

关系数据源

通过关系数据源,您可以轻松显示某条记录在关系端的数据源中的关联记录。

您可以在属性编辑器中为微件设置关系数据源。数据源对话框会向您显示为父模型配置的每个关系的关系数据源。请在将数据绑定到您的界面中了解如何在界面中选择关系数据源。请按照连接数据模型教程中的示例进行操作。

加载关联记录

系统在加载配置了关系数据源的微件时,会自动加载与当前记录关联的记录。在关系数据源上触发加载操作时,系统也会加载关联记录。

通常,您需要确保系统在应用使用数据之前加载关联记录。在系统加载某条记录的关联记录以后,如需运行脚本或收到通知,请对微件使用 onDataLoaded 事件。在您的应用中使用关联记录的常用方法如下:

  1. 创建一个不可见的面板,该面板使用关系数据源确保加载相关数据。
  2. 在这个不可见的面板上使用 onDataLoaded 事件执行一些使用相关数据的逻辑。

如需从服务器重新加载关联记录,请在关系数据源上调用 load。如果某个服务器脚本(而不是客户端脚本或绑定)更改了关联记录,您可能需要重新加载这些关联记录。

您还可以在当前记录上使用 _reload 方法重新加载关联记录。

创建模式数据源

您可以使用创建模式数据源在父查询数据源或关系数据源中创建一些项,这些项不会自动保存到数据库。相反,数据会存储在客户端的草稿记录中,直到用户触发保存记录的操作为止。草稿记录还可以为新记录提供模板。创建模式数据源与其父数据源属于同一模型。

使用创建模式数据源,您可以执行以下操作:

  • 将界面元素绑定到创建模式数据源中的字段。这些字段将使用值填充草稿记录。
  • 设置数据源记录的默认字段值。通过这种方法,必填字段始终具有值。创建模式数据源将记录保存到数据库以后,字段值会重置为默认值。您可以使用脚本或界面设置默认值。

您可以使用应用界面和客户端脚本处理创建模式数据源。

使用表单创建记录

默认情况下,输入类型表单中的值会绑定到创建模式数据源中的字段。用户填写表单时,应用制作工具会将值保存到草稿记录中。创建模式数据源为 @datasource.parent-datasource.modes.create,界面将其报告为父数据源(创建)。表单中的每个字段都会绑定到 datasource.item.field,该字段对应于草稿记录中的字段。

用户填写表单时,记录会保存在客户端上,直到用户点击 Submit为止。Submit 按钮的 onClick 操作为 datasource.item.createItem(),该操作会创建项并将其添加到父数据源。Clear 按钮的 onClick 操作为 widget.datasource.clearChanges(),该操作会清除已创建的数据源的记录。

请在数据微件:表单《使用数据》教程中了解详情。

使用脚本创建记录

客户端脚本可以将记录添加到创建模式数据源。如果您想执行以下操作,请以此方式添加记录:

  • 自动添加会话数据,例如用户名和创建记录的日期
  • 在您创建记录之前处理来自表单的数据,以便数据与数据源中的字段相匹配,例如连接名字和姓氏

例如,您有一个带有 DateAdded 字段的模型 People。如果您将创建模式数据源的记录的 DateAdded 属性设置为 new Date();,则通过该数据源创建的记录会将用户时区的当前日期作为开始日期:

var createDatasource = app.datasources.people.modes.create;
    var draft = createDatasource.item;
    draft.DateAdded = new Date();
    createDatasource.createItem(function(createdRecord) {
     alert('Created record on ' + createdRecord.DateAdded)
    });
    

详细了解 CreateDataSource