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-service和validate-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-options、list-options或options子元素的选项列表显示复选框(细节请查看drop-down的描述)。可选地用no-current-selected-key指定默认选中哪个框,或通过设置all-checked为true来选中所有框。
- date-find: 显示两个日期/时间输入小部件,就像有同样的type和format属性的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-options、list-options或option子元素组合选项列表,或用dynamic-options 元素来用一个到页面转换的请求获取可选选项。
- 使用 entity-options 从数据库记录中获取选项
- 用 key 属性指定用作键/值的实体字段,用 text 属性指定用作标签文本的实体字段。
- 用 entity-find 元素指定查询约束和选项,该元素同样用在XML动作脚本中。
- 对于选项来自一个Map列表,使用带Groovy表达式的 list-options 元素,该表达式求得的值放在list属性中的列表中,对应选项的键/值的Map键在key属性中,Map键的文本标签在text属性中。
- 要明确指定单独的选项,使用 option 元素,每个选项有 key 和 text 属性
- 对于 dynamic-options ,指定页面转换返回一个包含一个Map列表的JSON字符串, 从每个Map中获取值和标签的键名为value-field和label-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-options、list-options或option子元素(细节请看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-line 有 size、maxlength和default-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-ref、fields-not-referenced和field-row子元素来指定包含的字段,并可选地将他们放入一行中。
单表单示例
要对单表单的不同方面的功用有更好的理解,来看看一个更复杂的例子。这个表单是HiveMind项目管理应用的任务编辑页面。
这个表单有以下内容的示例(全部的源代码看下面):
- Project: 一个用entity-options填值的下拉框,和一个分开的link以去到当前与此任务相关的项目
- Milestone和Parent Task: 用 dynamic-options 填充的 drop-down 字段,都使用 depends-on 元素依赖于项目(rootWorkEffortId)
- Task Name: 简单的text-line输入框
- Resolution 和 Purpose: 标准的 Enumeration drop-down字段,使用有 set 子元素的widget-template-include 元素;Purpose使用一个小构件模板,由一个父枚举(parentEnumId)约束;而Resolution包含枚举类型(enumTypeId)中的所有值
- Status:标准状态下拉框,基于使用StatusFlowTransition实体的当前状态的转换上的选项
- Due Date: 简单的日期时间类型输入框
- Estimated Hours 和 Remaining 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)和只匹配该字段的记录的字段查找表头小部件。
列表表单表格的表体行是每条记录有一行,每行带删除按钮,但是更新按钮在底下,在单次表单提交中更新所有行以一次更新一批本地化的值。注意表头行中的查找(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>