XML表单

有两种类型的XML表单: single 和 list。single表单表示单个字段集,每个字段有label和widget。列表表单表现为表格,每个字段一列,表头中的标签(label),每行中的字段有一个widget,表单输出基于的列表中的每个条目占一行。

尽管有其他的方式获取数据,最常见的是一个single表单从一个Map获取字段值,一个list表单从一个Map的列表中获取数据。

XML表单像XML页面,都用一个FTL宏为每个元素进行渲染,都支持多种渲染模式。就像XML页面小部件,你可以通过添加宏来添加你自己的小部件。XML表单宏和XML页面宏一样在同样的FTL文件中,所以使用同样的实现来添加自定义宏。

表单字段

一个表单中的主要元素是field,由它的name属性标识。当一个表单扩展另一个表单,字段被用同样的字段名覆盖。对于HTML输出,这也是HTML表单字段的名字。此名字也被用作map的键或参数名以获取字段值(如果没有发现map键值,则可能是提交表单时出了错)。要在可用时仍然使用这个name,否则从上下文中其他某个地方获取字段值,可以使用entry-name属性,此属性可以是任意能求出期望值的Groovy表达式。

对于基于服务参数生成的HTML中的自动的客户端校验,你可以在field元素上使用validate-servicevalidate-parameter属性。当表单字段是基于使用auto-fields-service元素自动定义时,这两个属性将被自动填值。XML表单渲染器也将查看表单提交到的transition,如果它有单个service-call元素(而不是用actions元素处理输入)时,它将用匹配字段名的名字来查找服务的入参,并使用它的校验。

一个字段的字段类型或"widget"(视觉/交互元素),是field元素下的子元素。使用的默认小部件在default-field子元素下,所有的字段应当有一个(并且只有一个)。如果你想在特定条件下使用不同的小部件,使用带Groovy表达式的conditional-field元素,Groovy表达式在condition属性中,求得的值是布尔值。这在single和list表单中都可用,并且对于list表单会为每行求值。

在list表单的表头行,有一个field.header-field子元素。当使用时,这些表头字段小部件是被用于搜索的选项的和list表单分离的搜索表单的一部分。排序链接自然而然地伴随着list表单头中的搜索选项,可以通过设置header-field.show-order-by属性为true或case-insensitive来开启。

如果没有一个header-field元素,一个字段的标题来自 default-field.title属性;如果有,则来自header-field元素上的title属性。default-field元素也有一个tooltip属性,当字段获得焦点或鼠标在字段上时弹出一个提示框(依赖生成的HTML或其他特殊的表单渲染的特定行为)。

当还没有到开始日期或超出了结束日期时,日期值显示红色通常会比较好。这可以用 default-field.red-when属性来控制,它默认是字面意思,如果字段name是fromDate,那么是未来的日期时,字段是红色的;如果字段name是thruDate,那么是过去的日期时,字段是红色的。red-when属性也可以是before-now, after-now 和 never。

字段小部件(Field Widget)

对于表单字段,有一些自带的小部件,并且可以用宏模板和自定义元素 章节中描述的扩展机制添加额外的小部件。

页面中可用的任何小部件可被用在XML表单字段中(参阅XML页面Widget)。也有多个特定于表单字段的小构件。这是Moqui种自带的字段小部件的一个摘要:

  • auto-widget-service:基于 service-name服务的parameter-name入参自动定义字段小部件
    • 使用 field-type 属性来指定字段要使用的一般类型,基于参数对象的类型选择特定的字段小部件。可以是edit(默认)、find、display、find-display(增加find和display小部件)或hidden
  • auto-widget-entity: 基于 entity-name实体的field-name字段自动定义字段小部件。
    • 使用 field-type 属性来指定字段要使用的一般类型,基于参数对象的类型选择特定的字段小部件。可以是edit(默认)、find、display、find-display(增加find和display小部件)或hidden
  • widget-template-include:表单字段小部件模板在一个有widget-templates根元素的XML文件中定义。
    • 每个 widget-template 元素可以按需用${}参数包含任意字段小部件元素。
    • 要使用一个小部件模板,只要指定它的location并按为只在渲染此模板的范围内定义字段的需要设置子元素。
  • check: 为来自entity-optionslist-optionsoptions子元素的选项列表显示复选框(细节请查看drop-down的描述)。可选地用no-current-selected-key指定默认选中哪个框,或通过设置all-checked为true来选中所有框。
  • date-find: 显示两个日期/时间输入小部件,就像有同样的typeformat属性的date-time。使用default-value-from属性为起始时间(左边的)输入框设定默认值,default-value-thru属性为结束时间(右边的)输入框设定。
  • date-time:特定类型(timestamp、date-time、date或time)的日期/时间输入小部件。
    • format 属性中用一个Java SimpleDateFormat 字符串指定日期/时间字符串的格式
    • 小部件的文本输入框部分有 size 字符宽,单行,允许输入最多 maxlength 个字符,尽管这些是可选的,并且基于type被自动设置
    • 使用 default-value 属性来指定在上下文或参数中没有该字段时使用的值
  • display: 显示展开了的 text 属性值(如果为空则是字段值)的纯文本内容
    • 如果also-hidden没有被设置为false,则会添加一个对应的随表单提交的隐藏字段。
    • 使用 format 属性来为日期/时间(SimpleDateFormat)、数字(DecimalFormat)等值指定Java格式字符串。对于金额格式,指定包含金额currency-unit-field中的Uom.uomId的字段。
    • 对于HTML输出,如果encode没有被设置为false,默认会对文本编码
  • display-entity:为entity-name查找实体值,并显示包含实体字段值的扩展后的text
    • 这只限于通过单主键字段查找,如果实体的主键字段有一个不同于field.name的名字,则用key-field-name 属性指定它。
    • 默认这是一个被缓存的查询,如果不使用实体缓存,设置use-cache为false。
    • 就像 display
      • 如果also-hidden没被设置为false,有一个对应的隐藏字段随表单提交。
      • 对应HTML输出,如果encode没有被设置为false,默认会对文本编码
  • drop-down: 一个下拉框,或者在size被设置为大于1的数字时是一个多行下拉框.
    • 设置 allow-multiple 为 true,允许选择多个值
    • current
      • first-in-list(默认): 当前选中的值会在下拉框中的最前面,通过一个分隔线和其他的选项分开;
      • selected: 在选项中被选中
    • 设置 allow-empty 为 true,添加一个空选项到列表
    • entity-optionslist-optionsoption子元素组合选项列表,或用dynamic-options 元素来用一个到页面转换的请求获取可选选项。
    • 使用 entity-options 从数据库记录中获取选项
    • key 属性指定用作键/值的实体字段,用 text 属性指定用作标签文本的实体字段。
    • entity-find 元素指定查询约束和选项,该元素同样用在XML动作脚本中。
    • 对于选项来自一个Map列表,使用带Groovy表达式的 list-options 元素,该表达式求得的值放在list属性中的列表中,对应选项的键/值的Map键在key属性中,Map键的文本标签在text属性中。
      • 要明确指定单独的选项,使用 option 元素,每个选项有 keytext 属性
    • 对于 dynamic-options ,指定页面转换返回一个包含一个Map列表的JSON字符串, 从每个Map中获取值和标签的键名为value-fieldlabel-field属性。
      • 使用动态选项的主要原因是要在另一个字段变化时改变选项。要做到这,用一个或多个在field属性中带表单字段名的depends-on子元素。当引用的字段变化时,将请求页面转换,把所有引用字段的值作为参数传给请求,获得新的选项。
    • no-current-selected-key 属性中的键来设置默认选项。如果默认选项没在现有选项中,用current-description 属性指定它的描述
    • 默认使用一个动态drop-down小部件基于输入的文本来过滤可选项。要使用纯下拉,设置search为false。
    • 要允许用户输入不在下拉框中的新选项以提交,设置 combo-box 为true。
  • file: 一个文件上传输入框(有一个按钮/链接给文件选择弹出窗口
    • size(默认30)字符宽
    • 允许最多输入maxlength字符
    • 使用default-value属性来指定在上下文或参数中没有该字段的值时使用的值。
  • hidden:一个隐藏的字段,其值在提交的表单中传递,但是不显示给用户看。
    • 使用 default-value 属性来指定在上下文或参数中没有该字段的值时的默认值
  • ignored: 把该字段当作没有被定义。当扩展另一个表单来排除不要的字段时有用。
  • password: 一个密码输入框
    • size(默认30)字符宽
    • 允许最多输入maxlength字符
    • 为了安全,掩盖输入的内容
  • radio: 为来自 entity-optionslist-optionsoption子元素(细节请看drop-down的描述)的选项列表显示单选按钮。
    • 可选地用 no-current-selected-key 属性来指定默认选项的key
  • range-find:主要为了数字范围的查找,显示两个小的输入框
    • size(默认10)字符宽
    • 每个输入框允许最多输入maxlength字符
    • 使用 default-value-from 为从(左边)输入框指定默认值,default-value-thru 属性为到(右边)输入框指定默认值
  • reset: 一个重置表单的按钮。
    • 按钮上的文字来自字段标题
  • submit: 一个表单提交按钮.
    • 如果没有使用image子元素来放置一个图片在按钮上,那么按钮上的文字来自字段标题
    • 可使用icon属性从图标库设置一个风格的图标出现在按钮文字旁边(对于默认runtime的webroot,关于Bootstrap的Glyphicons是可用的,例如icon="glyphicon glyphicon-plus";或者用像"fa fa-search"这样使用Font Awesome图标)。
    • 如果要在按钮被按下时显示消息并询问用户确认,把消息放在confirmation属性中
  • text-line:一个简单文本输入框
    • size字符宽,单行,允许最多输入 maxlength 个字符
    • 使用default-value属性来指定在上下文或参数中没有该字段的值时使用的值。
    • 设置 disabled 为true来让输入框只读,不允许对值进行修改
    • 使用format属性来为日期/时间(SimpleDateFormat)、数字(DecimalFormat)等值指定Java格式字符串
    • text-line可以有自动完成功能,通过实现一个页面转换来提供值并在ac-transition属性中指定转换的名字
      • 转换的响应应该是一个JSON字符串(使用 ec.web.sendJsonResponse()),是有value和label字段的Map的列表。
      • 可选地用 ac-delay 指定延时,单位为毫秒(默认为300);用 ac-min-length指定在查询前需要输入的最少字符数(默认为1)
  • text-find
    • text-linesizemaxlengthdefault-value属性
    • 有一个复选框给ignore-case(默认为true,即选中状态)
    • 有一个下拉框给搜索运算符,在 default-operator属性中指定默认运算符(可以是equals、like、contains或empty)。
    • 忽略大小写的复选框和运算符下拉框也可以是隐藏的(默认以隐藏参数传递,没有可视的UI小部件),使用hide-options,可选的属性有false(默认的,两个都显示)、true(两个都隐藏)、ignore-case(只隐藏忽略大小写复选框)和operator(隐藏运算符下拉框)

单表单

使用 form-single 元素来定义单表单。form-single元素的属性有:

  • name: 表单的名字。用来和XML页面文件位置一起引用表单。对于HTML输出这是form的name和id,对于其他输出也可能被用来标识对应表单那部分的输出。
  • extends:扩展的表单的位置(location)和名字(name),用井号(#)分隔。如果没有位置,则被当作当前页面的一个表单的名字。
  • transition: 当前页面中表单提交到的转换
  • map: 从其中获取值的Map。经常是一个EntityValue对象或一个有从多个地方拉取的数据的Map,以填值到表单。Map的键和字段名匹配。如果field.entry-name属性被使用,将忽略此属性,从每个字段被渲染时的上下文中取值到表单。
  • focus-field:当表单被渲染时,获得焦点的字段的name
  • skip-start: 忽略渲染表单的开始元素(<form .. >)。当用在一个使用了skip-end=true的表单之后时,将有效地把两个表单合成一个
  • skip-end: 忽略渲染表单结束元素(<form/>)。使用这个来让表单保持开放,这样额外的表单可以与之组合。
  • dynamic: 如果为true,表单将被当作动态,并且每次使用时都会重新构建内部定义,而不是只在第一次被引用时构建。当auto-fields- 元素有${}字符串为服务或实体名扩展时,这是必需的。
  • background-submit: 在后台提交表单,不重新加载页面
  • background-reload-id: 表单在后台提交后,重新加载有这个id的dynamic-container
  • background-message: 表单在后台提交后,在一个对话框中显示此消息

要以一种不是纯字段列表的方式对字段布局,使用form-single.field-layout元素。对于HTML输出,有一个可选的id属性来帮助定制样式。如果字段布局包含字段组,设置collapsible属性为true来使用一个手风琴小部件来节省空间,可选地指定active组的索引而不是第一个,这样来设置初始时打开哪一个。这是定义布局的子元素:

  • field-ref: 通过字段名指定在哪里包含一个字段
  • fields-not-referenced: 包含没有在其他地方引用到的所有字段;如果没有这个元素,那些在字段布局中没有被引用到的字段将不会被渲染
  • field-row: 创建由field-ref子元素指定的一行字段;如果这行中有两个字段,将会显示4列,都有标题;如果有多于2个字段,只有第一个字段的标题会被显示,其他字段小部分在行中依次排列,按需换行
  • field-group: 创建一组字段,如果field-layout.collapsible为true,则放入一个手风琴控件,用一个可选的title作为组的标题,对于HTML输出,一个可选的style给包含组的容器(div);使用field-reffields-not-referencedfield-row子元素来指定包含的字段,并可选地将他们放入一行中。

单表单示例

要对单表单的不同方面的功用有更好的理解,来看看一个更复杂的例子。这个表单是HiveMind项目管理应用的任务编辑页面。

这个表单有以下内容的示例(全部的源代码看下面):

  • Project: 一个用entity-options填值的下拉框,和一个分开的link以去到当前与此任务相关的项目
  • MilestoneParent Task: 用 dynamic-options 填充的 drop-down 字段,都使用 depends-on 元素依赖于项目(rootWorkEffortId
  • Task Name: 简单的text-line输入框
  • ResolutionPurpose: 标准的 Enumeration drop-down字段,使用有 set 子元素的widget-template-include 元素;Purpose使用一个小构件模板,由一个父枚举(parentEnumId)约束;而Resolution包含枚举类型(enumTypeId)中的所有值
  • Status:标准状态下拉框,基于使用StatusFlowTransition实体的当前状态的转换上的选项
  • Due Date: 简单的日期时间类型输入框
  • Estimated HoursRemaining Hours: 简单的数字输入框
  • Actual Hours:按数字formmat字符串显示的display
  • Description: 简单的 text-area

示例页面

这个表单使用 field-layout 来将各字段肩并肩放置,否则使用默认布局。有 field-group 手风琴式布局的例子,请看Moqui的Example应用中的"Edit Example"页面。下面是这个单表单的源代码,可以看到transition的定义,页面actions做数据准备等等:

<screen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/xml-screen-1.4.xsd"
  default-menu-title="Task" default-menu-index="1">
  <parameter name="workEffortId"/>
  <transition name="updateTask">
    <service-call name="mantle.work.TaskServices.update#Task"
      in-map="context"/>
    <default-response url="."/>
  </transition>
  <transition name="editProject">
    <default-response url="../../Project/EditProject"/>
  </transition>
  <transition name="milestoneSummary">
    <default-response url="../../Project/MilestoneSummary"/>
  </transition>
  <transition name="getProjectMilestones">
    <actions>
      <service-call in-map="context" out-map="context"
        name="mantle.work.ProjectServices.get#ProjectMilestones"/>
      <script>ec.web.sendJsonResponse(resultList)</script>
    </actions>
    <default-response type="none"/>
  </transition>
  <transition name="getProjectTasks">
    <actions>
      <service-call in-map="context" out-map="context"
        name="mantle.work.ProjectServices.get#ProjectTasks"/>
      <script>ec.web.sendJsonResponse(resultList)</script>
    </actions>
    <default-response type="none"/>
  </transition>
  <actions>
    <entity-find-one entity-name="mantle.work.effort.WorkEffort"
      value-field="task"/>
    <entity-find-one entity-name="mantle.work.effort.WorkEffort"
      value-field="project">
      <field-map field-name="workEffortId" from="task.rootWorkEffortId"/>
    </entity-find-one>
    <entity-find entity-name="mantle.work.effort.WorkEffortAssoc"
      list="milestoneAssocList">
      <date-filter/>
      <econdition field-name="toWorkEffortId" from="task.workEffortId"/>
      <econdition field-name="workEffortAssocTypeEnumId"
        value="WeatMilestone"/>
    </entity-find>
    <set field="milestoneAssoc" from="milestoneAssocList?.getAt(0)"/>
    <set field="statusFlowId"
      from="(task.statusFlowId ?: project.statusFlowId) ?: 'Default'"/>
  </actions>
  <widgets>
    <form-single name="EditTask" transition="updateTask" map="task">
      <field name="workEffortId">
        <default-field title="Task ID">
          <display/>
        </default-field>
      </field>
      <field name="rootWorkEffortId">
        <default-field title="Project">
          <drop-down>
            <entity-options key="${workEffortId}"
              text="${workEffortId}: ${workEffortName}">
              <entity-find entity-name="WorkEffortAndParty">
                <date-filter/>
                <econdition field-name="partyId"
                  from="ec.user.userAccount.partyId"/>
                <econdition field-name="workEffortTypeEnumId"
                  value="WetProject"/>
              </entity-find>
            </entity-options>
          </drop-down>
          <link text="Edit ${project.workEffortName} [${task.rootWorkEffortId}]"
            url="editProject">
          <parameter name="workEffortId" from="task.rootWorkEffortId"/>
          </link>
        </default-field>
      </field>
      <field name="milestoneWorkEffortId"
        entry-name="milestoneAssoc?.workEffortId">
        <default-field title="Milestone">
          <drop-down combo-box="true">
            <dynamic-options transition="getProjectMilestones"
              value-field="workEffortId" label-field="milestoneLabel">
              <depends-on field="rootWorkEffortId"/>
            </dynamic-options>
          </drop-down>
          <link url="milestoneSummary"
            text="${milestoneAssoc ? 'Edit ' + milestoneAssoc.workEffortId : ''}">
          <parameter name="milestoneWorkEffortId"
            from="milestoneAssoc?.workEffortId"/>
          </link>
        </default-field>
      </field>
      <field name="parentWorkEffortId">
        <default-field title="Parent Task">
          <drop-down combo-box="true">
            <dynamic-options transition="getProjectTasks"
              value-field="workEffortId" label-field="taskLabel">
              <depends-on field="rootWorkEffortId"/>
            </dynamic-options>
          </drop-down>
        </default-field>
      </field>
      <field name="workEffortName">
        <default-field title="Task Name">
          <text-line/>
        </default-field>
      </field>
      <field name="priority">
        <default-field>
          <widget-template-include location="component://HiveMind/template/
            screen/ProjectWidgetTemplates.xml#priority"/>
        </default-field>
      </field>
      <field name="purposeEnumId">
        <default-field title="Purpose">
          <widget-template-include location="component://webroot/template/
            screen/BasicWidgetTemplates.xml#enumWithParentDropDown">
            <set field="enumTypeId" value="WorkEffortPurpose"/>
            <set field="parentEnumId" value="WetTask"/>
          </widget-template-include>
        </default-field>
      </field>
      <field name="statusId">
        <default-field title="Status">
          <widget-template-include location="component://webroot/template/screen/BasicWidgetTemplates.xml#statusTransitionWithFlowDropDown">
            <set field="currentDescription"
              from="task?.'WorkEffort#moqui.basic.StatusItem'?.description"/>
            <set field="statusId" from="task.statusId"/>
          </widget-template-include>
        </default-field>
      </field>
      <field name="resolutionEnumId">
        <default-field title="Resolution">
          <widget-template-include location="component://webroot/template/
            screen/BasicWidgetTemplates.xml#enumDropDown">
            <set field="enumTypeId" value="WorkEffortResolution"/>
          </widget-template-include>
        </default-field>
      </field>
      <field name="estimatedCompletionDate">
        <default-field title="Due Date">
          <date-time type="date" format="yyyy-MM-dd"/>
        </default-field>
      </field>
      <field name="estimatedWorkTime">
        <default-field title="Estimated Hours">
          <text-line size="5"/>
        </default-field>
      </field>
      <field name="remainingWorkTime">
        <default-field title="Remaining Hours">
          <text-line size="5"/>
        </default-field>
      </field>
      <field name="actualWorkTime">
        <default-field title="Actual Hours">
          <display format="#.00"/>
        </default-field>
      </field>
      <field name="description">
        <default-field title="Description">
          <text-area rows="20" cols="100"/>
        </default-field>
      </field>
      <field name="submitButton">
        <default-field title="Update">
          <submit/>
        </default-field>
      </field>
      <field-layout>
        <fields-not-referenced/>
        <field-row>
          <field-ref name="purposeEnumId"/>
          <field-ref name="priority"/>
        </field-row>
        <field-row>
          <field-ref name="statusId"/>
          <field-ref name="estimatedCompletionDate"/>
        </field-row>
        <field-row>
          <field-ref name="estimatedWorkTime"/>
          <field-ref name="remainingWorkTime"/>
        </field-row>
        <field-ref name="actualWorkTime"/>
        <field-ref name="description"/>
        <field-ref name="submitButton"/>
      </field-layout>
    </form-single>
  </widgets>
</screen>

列表表单

使用 form-list 元素来定义一个列表表单。这些是 form-list 元素的属性:

  • name: 表单的名字。和XML页面文件的位置一起被用来引用此表单。对于HTML输出,这是form的name和id;对于其他输出也可能被用来标识对应表单那部分的输出。
  • extends:扩展的表单的位置(location)和名字(name),用井号(#)分隔。如果没有位置,则被当作当前页面的一个表单的名字。
  • transition: 当前页面中表单提交到的转换
  • multi: 使得表单为一个multi-submit表单,一页中的所有行被放在单个请求中一起提交,每个字段有一个"_${rowNumber}"后缀。也传递一个_isMulti=true 参数,这样ServiceFacade指定为每行都运行服务(transition中的单个service-call)。默认为true,设置为false来禁止此行为并为每行提供一个分离的表单(分开提交)
  • list:一个表达式,值为列表,以供遍历
  • list-entry: 如果被指定,每个列表条目将被用此名字放入上下文,否则列表条目必须为一个Map,map中的条目将被为每行放入上下文
  • paginate: 标示这个表单是否要分页。默认为true。
  • paginate-always-show: 总是显示分页控制,有行数信息、即使只有一页也显示分页控制。默认为true。
  • skip-start: 忽略渲染表单的开始元素(<form .. >)。当用在一个使用了skip-end=true的表单之后时,将有效地把两个表单合成一个
  • skip-end: 忽略渲染表单结束元素(<form/>)。使用这个来让表单保持开放,这样额外的表单可以与之组合。
  • skip-form: 只输出一个纯表格,不可提交(在HTML中不生成form元素),对只读列表表单最小化输出有用
  • dynamic: 如果为true,表单将被当作动态,并且每次使用时都会重新构建内部定义,而不是只在第一次被引用时构建。当auto-fields- 元素有${}字符串为服务或实体名扩展时,这是必需的。

类似于单表单中的field-layout,列表表单有一个form-list-column元素。当使用时,列表表单表格中的每列需要有一个,所有字段必须在某一列中被引用,否则将不会显示。form-list-column元素有单个子元素,就是被用在单表单 field-layout 中的同样的 field-ref 元素。

表单的数据准备最好是在使用它的XML页面中的actions中完成,但是有时你需要为列表表单中的每一行准备数据。这可通过预先准备一个有条目对应每个列表表单字段的Map对象列表来完成。用这种方法,准备列表的逻辑可以做额外的数据查找或计算来准备数据。另一个实现是将XML动作放在form-list.row-actions元素下。这些动作将为每行在一个隔离的上下文中运行,这样定义的任意上下文字段将只被用于那一行。

列表表单:查看/导出示例

主要有两类列表表单:用于搜索、查看和导出的,和用于在一个页面中编辑一些记录的。

在Moqui的Tools应用中Artifact Summary页面是一个用于搜索、查看数据并导出结果到CSV、XML和PDF文件,且都使用同一个页面和表单定义的好例子。页面上的列表表单为每个构件显示一行,有moqui.server.ArtifactHitBin记录的概要(使用moqui.server.ArtifactHitReport view-entity)。

列表表单示例图

注意左上角的"Get as CSV"链接(还有类似的XML和PDF链接)。这些链接去到简单的ArtifactHitSummaryStats.csv转换,该转换去到同一个页面并添加renderMode=csv、pageNoLimit=true和lastStandalone=true参数以便页面渲染器输出csv而不是html,分页被禁止(即输出所有结果),并且只渲染页面层级中的最后一个页面(跳过所有父页面以避免装饰等,这个最后的页面是"standalone")。更多细节请查看XML、CSV和纯文本处理章节。

在"Get as"链接下是分页控制,它默认是开启的,在要显示多于一页结果时展示。在表头行的是列标题和为了在每列中排序结果的"+-"链接,加上一个构件类型下拉框和一个text-find构件名字查找框。这些都定义在每个字段下的header-field元素中。

此表单使用form-list.row-actions元素来为每行计算平均时间,然后用一个表单字段显示。

这是ArtifactHitSummary.xml的源代码,展示了上面概述的的细节:

<screen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/xml-screen-1.4.xsd"
  default-menu-title="Artifact Summary">
  <transition name="ArtifactHitSummaryStats.csv">
    <default-response url=".">
      <parameter name="renderMode" value="csv"/>
      <parameter name="pageNoLimit" value="true"/>
      <parameter name="lastStandalone" value="true"/>
    </default-response>
  </transition>
  <transition name="ArtifactHitSummaryStats.xml">
    <default-response url=".">
      <parameter name="renderMode" value="xml"/>
      <parameter name="pageNoLimit" value="true"/>
      <parameter name="lastStandalone" value="true"/>
    </default-response>
  </transition>
  <transition name="ArtifactHitSummaryStats.pdf">
    <default-response url-type="plain"
      url="${ec.web.getWebappRootUrl(false, null)}/fop/apps/tools/System/
      ArtifactHitSummary">
      <parameter name="renderMode" value="xsl-fo"/>
      <parameter name="pageNoLimit" value="true"/>
    </default-response>
  </transition>
  <actions>
    <entity-find entity-name="moqui.server.ArtifactHitReport"
      list="artifactHitReportList" limit="50">
      <search-form-inputs default-order-by="artifactType,artifactName"/>
    </entity-find>
  </actions>
  <widgets>
    <container>
      <link url="ArtifactHitSummaryStats.csv" text="Get as CSV"
        target-window="_blank" expand-transition-url="false"/>
      <link url="ArtifactHitSummaryStats.xml" text="Get as XML"
        target-window="_blank" expand-transition-url="false"/>
      <link url="ArtifactHitSummaryStats.pdf" text="Get as PDF"
        target-window="_blank"/>
    </container>
    <form-list name="ArtifactHitSummaryList" list="artifactHitReportList">
      <row-actions>
        <set field="averageTime" from="(totalTimeMillis/hitCount as
          BigDecimal).setScale(0,BigDecimal.ROUND_UP)"/>
      </row-actions>
      <field name="artifactType">
        <header-field show-order-by="true">
          <drop-down allow-empty="true">
            <option key="screen"/>
            <option key="screen-content"/>
            <option key="transition"/>
            <option key="service"/>
            <option key="entity"/>
          </drop-down>
        </header-field>
        <default-field><display also-hidden="false"/></default-field>
      </field>
      <field name="artifactName">
        <header-field show-order-by="true">
          <text-find hide-options="true" size="20"/>
        </header-field>
        <default-field>
          <display text="${artifactName}"
            also-hidden="false"/>
        </default-field>
      </field>
      <field name="lastHitDateTime">
        <header-field title="Last Hit" show-order-by="true"/>
        <default-field>
          <display also-hidden="false"/>
        </default-field>
      </field>
      <field name="hitCount">
        <header-field title="Hits" show-order-by="true"/>
        <default-field>
          <display also-hidden="false"/>
        </default-field>
      </field>
      <field name="minTimeMillis">
        <header-field title="Min" show-order-by="true"/>
        <default-field>
          <display also-hidden="false"/>
        </default-field>
      </field>
      <field name="averageTime">
        <default-field title="Avg">
          <display also-hidden="false"/>
        </default-field>
      </field>
      <field name="maxTimeMillis">
        <header-field title="Max" show-order-by="true"/>
        <default-field>
          <display also-hidden="false"/>
        </default-field>
      </field>
      <field name="find">
        <header-field title="Find">
          <submit/>
        </header-field>
      </field>
    </form-list>
  </widgets>
</screen>

列表表单:编辑示例

Moqui的Tools应用中的Entity Fields Localization页面是一个用来在单个页面中更新多条记录的列表表单的好例子。此页面被设计为添加、编辑和删除moqui.basic.LocalizedEntityField记录,该记录指定要使用的本地化的文本,而不是实体记录字段的实际值。

在下面的截屏中,在左上角有一个按钮在一个container-dialog模态弹出框中添加新记录。在其下是默认开启的分页控制。表单中的表头行有字段标题(因为没有header-field.title属性,在本例中都基于字段名生成),"+-"排序链接(header-field.show-order-by=true)和只匹配该字段的记录的字段查找表头小部件。

Entity Fields Localization页面截屏

列表表单表格的表体行是每条记录有一行,每行带删除按钮,但是更新按钮在底下,在单次表单提交中更新所有行以一次更新一批本地化的值。注意表头行中的查找(Find)按钮,是和每行表体上的删除按钮在同一列。要做成这样,在表单定义中,查找按钮被定义在删除字段的header-field元素的子元素中。

下面是EntityFields.xml页面的源代码。创建、更新和删除转换使用隐藏定义的entity-auto服务,所以没有它们的服务定义或实现。此功能只依赖一个XML页面和LocalizedEntityField实体的定义。

<screen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/xml-screen-1.4.xsd"
  default-menu-title="Entity Fields" default-menu-index="2">
  <transition name="createLocalizedEntityField">
    <service-call name="create#moqui.basic.LocalizedEntityField"/>
    <default-response url="."/>
  </transition>
  <transition name="updateLocalizedEntityField">
    <service-call name="update#moqui.basic.LocalizedEntityField"
      multi="true"/>
    <default-response url="."/>
  </transition>
  <transition name="deleteLocalizedEntityField">
    <service-call name="delete#moqui.basic.LocalizedEntityField"/>
    <default-response url="."/>
  </transition>
  <actions>
    <entity-find entity-name="moqui.basic.LocalizedEntityField"
      list="localizedEntityFieldList" offset="0" limit="50">
      <search-form-inputs default-order-by="entityName,fieldName,locale"/>
    </entity-find>
  </actions>
  <widgets>
    <container>
      <container-dialog id="CreateEntityFieldDialog"
        button-text="New Field L10n">
        <form-single name="CreateLocalizedEntityField"
          transition="createLocalizedEntityField">
          <field name="entityName">
            <default-field>
              <text-line size="15"/>
            </default-field>
          </field>
          <field name="fieldName">
            <default-field>
              <text-line size="15"/>
            </default-field>
          </field>
          <field name="pkValue">
            <default-field>
              <text-line size="20"/>
            </default-field>
          </field>
          <field name="locale">
            <default-field>
              <text-line size="5"/>
            </default-field>
          </field>
          <field name="localized">
            <default-field>
              <text-area rows="5" cols="60"/>
            </default-field>
          </field>
          <field name="submitButton">
            <default-field title="Create">
              <submit/>
            </default-field>
          </field>
        </form-single>
      </container-dialog>
    </container>
    <form-list name="UpdateLocalizedEntityFields"
      list="localizedEntityFieldList"
      transition="updateLocalizedEntityField" multi="true">
      <field name="entityName">
        <header-field show-order-by="true">
          <text-find hide-options="true" size="12"/>
        </header-field>
        <default-field>
          <display/>
        </default-field>
      </field>
      <field name="fieldName">
        <header-field show-order-by="true">
          <text-find hide-options="true" size="12"/>
        </header-field>
        <default-field>
          <display/>
        </default-field>
      </field>
      <field name="pkValue">
        <header-field show-order-by="true">
          <text-find hide-options="true" size="12"/>
        </header-field>
        <default-field>
          <display/>
        </default-field>
      </field>
      <field name="locale">
        <header-field show-order-by="true">
          <text-find hide-options="true" size="4"/>
        </header-field>
        <default-field>
          <display/>
        </default-field>
      </field>
      <field name="localized">
        <default-field>
          <text-area rows="2" cols="35"/>
        </default-field>
      </field>
      <field name="update">
        <default-field title="Update">
          <submit/>
        </default-field>
      </field>
      <field name="delete">
        <header-field title="Find">
          <submit/>
        </header-field>
        <default-field>
          <link text="Delete" url="deleteLocalizedEntityField">
          <parameter name="entityName"/>
          <parameter name="fieldName"/>
          <parameter name="locale"/>
          </link>
        </default-field>
      </field>
    </form-list>
  </widgets>
</screen>

results matching ""

    No results matching ""