转自 http://www.moqui.org/framework/docs/Tutorial.html
本教程是一步一步的指导,建立和运行自己的Moqui组件的用户界面,逻辑,与数据库交互。
jiasudu 15000850008
概述
第1部分
下载Moqui框架
新建一个组件
添加Screen
添加Subscreen
添加显示的内容
添加子内容
第2部分
第一个实体定义
添加一些数据
自动查询Form
一个明确的字段
添加Create Form
第3部分
创建自定义Service
Groovy服务
下一步是什么?
jiasudu 15000850008
概述
第1:创建一个新的component和一个“Hello world!的screen。
第2:定义新的实体(数据库表)和form添加到screen,并创建该实体。
第3:创建一些自定义的逻辑,而不是使用默认的CRUD,对实体进行操作。
本文档中使用的方法是一个简单的使用内嵌的servlet容器。更完整部署方法,请阅读运行和部署文件。
第1部分
下载Moqui框架
下载Moqui框架. 下载之后在 moqui-<version>目录下,有 moqui-<version>.war 文件 和默认 runtime 目录 .
在Moqui根目录下.
如果你是全新下载, 你可以加载数据和运行服务器:
$ ant load
$ ant run
在浏览器输入 http://localhost:8080/, 使用 John Doe登陆, 可以浏览系统.
在命令行输入(<ctrl>-c ) 停止系统。
新建Component
Moqui 遵循 "约定优于配置" 的原则, 你可以新建一个 Moqui component 在下面目录:
$ cd runtime/component
$ mkdir tutorial
然后进入目录,创建其他标准目录:
$ cd tutorial
$ mkdir data
$ mkdir entity
$ mkdir screen
$ mkdir script
$ mkdir service
然后重启 Moqui (或者使用 $ ant run ).
jiasudu 15000850008
新建 Screen
使用 IDE 或者文本编辑器新增一个 screen 的XML 文件:
runtime/component/tutorial/screen/tutorial.xml
然后我们编写一个最简单的screen,里面只显示 "Hello world!" . 内容如下 :
1
2
3
4
5
6
7
8
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
require-authentication
=
"false"
>
<
widgets
>
<
label
type
=
"h1"
text
=
"Hello world!"
/>
</
widgets
>
</
screen
>
|
新增一个Subscreen
screen必须添加为根screen的子屏幕才是可以使用的。Moqui screens 的URL路径和菜单结构是对应的,所以需要设置URL给Screen和添加一个菜单tab。
在本教程中,我们将使用根 screen和header/footer/etc ,。runtime目录下有一个webroot的组件和webroot的Screen:
runtime/component/webroot/screen/webroot.xml
On a side note, the root screen is specified in the Moqui Conf XML file using the webapp-list.webapp.root-screen element, and you can have multiple elements to have different root screens for different host names.
To make the subscreen hierarchy more flexible this root screen only has a basic HTML head and body, with no header and footer content, so let's put our screen under the "apps" screen which adds a header menu and will give our screen some context. Modify the apps screen by changing:
runtime/component/webroot/screen/webroot/apps.xml
在apps.xml 新增subscreens元素,在subscreens 里面新增subscreens-item元素:
1
2
|
<
subscreens-item
name
=
"tutorial"
menu-title
=
"Tutorial"
|
screen的name属性对应Url的路径 , 所以你可以在浏览器上使用下面的url:
http://localhost:8080/apps/tutorial
如果你不想修改已经存在的 screen 文件 并且仍然想添加新的子screen,你可以编辑一条数据库记录:
1
2
3
4
|
subscreenName
=
"tutorial"
userGroupId
=
"ALL_USERS"
menuTitle
=
"Tutorial"
menuIndex
=
"1"
menuInclude
=
"Y"
/>
|
添加显示内容
Instead of using the label element we can get the HTML from a file that is "under" the screen.
新建一个简单的HTML 文件:
runtime/component/tutorial/screen/tutorial/hello.html
这个 HTML文件包含 一些HTML元素, 采用 included 父screen的 header/footer/etc :
1
|
<
h1
>Hello world! (from hello.html file)</
h1
>
|
现在include这个HTML 文件到 tutorial.xml screen,使用render-mode.text元素:
1
2
3
4
5
6
7
8
9
10
11
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
widgets
>
<
label
type
=
"h1"
text
=
"Hello world!"
/>
<
render-mode
>
<
text
type
=
"html"
</
render-mode
>
</
widgets
>
</
screen
>
|
So what is this render-mode thingy? Moqui XML Screens are meant to platform agnostic and may be rendered in various environments. Because of this we don't want anything in the screen that is specific to a certain mode of rendering the screen without making it clear that it is. Under the render-mode element you can have various sub-elements for different render modes, even for different text modes such as HTML, XML, XSL-FO, CSV, and so on so that a single screen definition can be rendered in different modes and produce output as needed for each mode.
The screen is available at the same URL, but now includes the content from the HTML file instead of having it inline as a label in the screen definition.
添加子内容
Another way to show the contents fo the hello.html file is to treat it as screen sub-content.
To do this the hello.html file must by in a sub-directory with the same name as the screen, ie in a tutorialdirectory as a sibling of the tutorial.xml file.
Now all we have to do is:
- tell the tutorial.xml screen to include child content by setting the screen.@include-child-contentattribute to true
- tell the screen where to include subscreens and child content by adding a widgets.subscreens-active element
screen XML 文件如下:
1
2
3
4
5
6
7
8
9
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
include-child-content
=
"true"
>
<
widgets
>
<
label
type
=
"h1"
text
=
"Hello world!"
/>
<
subscreens-active
/>
</
widgets
>
</
screen
>
|
To see the content now you'll need to go to a different URL to tell Moqui that you want the hello.html file that is under the tutorial screen:
http://localhost:8080/apps/tutorial/hello.html
第二部分
第一个实体定义
一个 entity 是一个二维的数据结构, 并对应数据库的一张表结构. 一个entity值对应在数据库中为一行. Moqui 没有做ORM, 以致我们必须定义一个entity, 然后调用 Entity Facade写代码 (或其他更高级的公斤or other higher level tools) 来调用他.
创建一个简单的实体"Tutorial" ,里面有2个字段 "tutorialId" 和 "description" 。新建 entity XML 文件:
runtime/component/tutorial/entity/TutorialEntities.xml
内容:
1
2
3
4
5
6
7
8
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
entity
entity-name
=
"Tutorial"
package-name
=
"tutorial"
>
<
field
name
=
"tutorialId"
type
=
"id"
is-pk
=
"true"
/>
<
field
name
=
"description"
type
=
"text-long"
/>
</
entity
>
</
entities
>
|
If you're running Moqui in dev mode the entity definition cache clears automatically so you don't have to restart, and for production mode or if you don't want to wait (since Moqui does start very fast) you can just stop and start the JVM.
How do you create the table? Unless you turn the feature off (in the Moqui Conf XML file) the Entity Facade will create the table (it it doesn't already exist) the first time the entity is used.
添加数据
The Entity Facade has functionality to load data from, and write data to, XML files that basically elements that match entity names and attributes that map field names.
我们随后可以创建UI界面录入数据, 你也可以用系统工具中的自动 Screen或Entity数据UI 录入该实体的数据. 被数据模型使用的数据文件有 程序依赖的种子数据,测试数据 , 和演示数据.
创建一个 entity facade的 XML 文件:
runtime/component/tutorial/data/TutorialData.xml
内容如下:
1
2
3
4
5
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
entity-facade-xml
type
=
"seed"
>
<
Tutorial
tutorialId
=
"TestOne"
description
=
"Test one description."
/>
<
Tutorial
tutorialId
=
"TestTwo"
description
=
"Test two description."
/>
</
entity-facade-xml
>
|
为了加载数据,我们可以执行一下 $ ant load 也可以查看 Run and Deploy文档采用其他方式执行.
自动查询Form
新增 XML screen 定义作为子页面,添加到tutorial screen 中:
runtime/component/tutorial/screen/tutorial/FindTutorial.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
transition
name
=
"findTutorial"
><
default-response
url
=
"."
/></
transition
>
<
actions
>
<
entity-find
entity-name
=
"Tutorial"
list
=
"tutorialList"
>
<
search-form-inputs
/>
</
entity-find
>
</
actions
>
<
widgets
>
<
form-list
name
=
"ListTutorials"
list
=
"tutorialList"
transition
=
"findTutorial"
>
<
auto-fields-entity
entity-name
=
"Tutorial"
field-type
=
"find-display"
/>
</
form-list
>
</
widgets
>
</
screen
>
|
screen 分为以下几部分:
- transition Think of links between screens as an ordered graph where each screen is a node and the transitions defined in each screen are how you go from that screen to another (or back to the same), and as part of that transition possibly run actions or a service.
- A single transition can have multiple responses with conditions and for errors resulting in transition to various screens as needed by your UI design.
- This particular transition very simply just refers back to the current screen.
- actions.entity-find There is just one action run when this screen is rendered: an entity-find.
- Normally with an entity-find element (or in the Java API an EntityFind object) you would specify conditions, fields to order by, and other details about the find to run.
- In this case we are doing a find on an entity using standard parameters from an XML Form, so we can use the search-form-inputs sub-element to handle these automatically.
- To get an idea of what the parameters should be like just view the HTML source in your browser that is generated by the XML Form.
- widgets.form-list This is the actual form definition, specifically for a "list" form for multiple records/rows (as opposed to a "single" form).
- The name here can be anything as long as it is unique within the XML Screen.
- Note that the list refers to the result of the entity-find in the actions block, and the transitionattribute refers to the transition defined at the top of the screen.
- Since the goal was to have a form automatically defined based on an entity we use the auto-fields-entity element with the name of our Tutorial entity, and find-display option for the field-typeattribute which creates find fields in the header and display fields for each record in the table body.
用下面的URL看看这个screen:
http://localhost:8080/apps/tutorial/FindTutorial
一个明确的字段
Instead of the default for the description field, what if you wanted to specify how it should look at what type of field it should be?
To do this just add a field element inside the form-list element, and just after the auto-fields-entityelement, like this:
1
2
3
4
5
6
7
8
9
|
<
form-list
name
=
"ListTutorials"
list
=
"tutorialList"
transition
=
"findTutorial"
>
<
auto-fields-entity
entity-name
=
"Tutorial"
field-type
=
"find-display"
/>
<
field
name
=
"description"
>
<
header-field
show-order-by
=
"true"
>
<
text-find
hide-options
=
"true"
/>
</
header-field
>
<
default-field
><
display
/></
default-field
>
</
field
>
</
form-list
>
|
Because the field name attribute is the same as a field already created by the auto-fields-entity element it will override that field. If the name was different an additional field would be created. The result of this is basically the same as what was automatically generated using the auto-fields-entity element, and this is how you would do it explicitly.
新建 Create Form
Let's add a button that will pop up a Create Tutorial form, and a transition to process the input.
First add the transition to the FindTutorial.xml screen you created before, right next to the findTutorialtransition:
1
2
3
4
|
<
transition
name
=
"createTutorial"
>
<
service-call
name
=
"create#Tutorial"
/>
<
default-response
url
=
"."
/>
</
transition
>
|
This transition just calls the create#Tutorial service, and then goes back to the current screen.
Where did the create#Tutorial service come from? We haven't defined anything like that yet. The Moqui Service Facade supports a special kind of service for entity CrUD operations that don't need to be defined, let alone implemented. This service name consists of two parts, a verb and a noun, separated by a hash (#). As long as the verb is create, update, store, or delete and the noun is a valid entity name the Service Facade will treat it as an implicit entity-auto service and do the desired operation. It does so based on the entity definition and the parameters passed to the service call. For example, with the create verb and an entity with a single primary key field if you pass in a value for that field it will use it, otherwise it will automatically sequence a value using the entity name as the sequence key.
Next let's add the create form, in a hidden container that will expand when a button is clicked. Put this inside the widget element, just above the form-list element in the original FindTutorial screen you created before so that it appears above the list form in the screen:
1
2
3
4
5
6
7
8
|
<
container-dialog
id
=
"CreateTutorialDialog"
button-text
=
"Create Tutorial"
>
<
form-single
name
=
"CreateTutorial"
transition
=
"createTutorial"
>
<
auto-fields-entity
entity-name
=
"Tutorial"
field-type
=
"edit"
/>
<
field
name
=
"submitButton"
>
<
default-field
title
=
"Create"
><
submit
/></
default-field
>
</
field
>
</
form-single
>
</
container-dialog
>
|
The form definition refers to the transition you just added to the screen, and uses the auto-fields-entityelement with edit for the field-type to generate edit fields. The last little detail is to declare a button to submit the form, and it's ready to go. Try it out and see the records appear in the list form that was part of the original screen.
第三部分
创建自定义Service
The createTutorial transition from our screen above used the implicit entity-auto service create#Tutorial. Let's see what it would look like to define and implement a service manually.
First lets define a service and use the automatic entity CrUD implementation:
runtime/component/tutorial/service/tutorial/TutorialServices.xml
1
2
3
4
5
6
7
8
|
<
service
verb
=
"create"
noun
=
"Tutorial"
type
=
"entity-auto"
>
<
in-parameters
>
<
auto-parameters
include
=
"all"
/>
</
in-parameters
>
<
out-parameters
>
<
auto-parameters
include
=
"pk"
required
=
"true"
/>
</
out-parameters
>
</
service
>
|
This will allow all fields of the Tutorial entity to be passed in, and will always return the PK field (tutorialId). Note that with the auto-parameters element we are defining the service based on the entity, and if we added fields to the entity they would be automatically represented in the service.
Now change that service definition to add an inline implementation as well. Notice that the service.@typeattribute has changed, and the actions element has been added.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<
service
verb
=
"create"
noun
=
"Tutorial"
type
=
"inline"
>
<
in-parameters
>
<
auto-parameters
include
=
"all"
/>
</
in-parameters
>
<
out-parameters
>
<
auto-parameters
include
=
"pk"
required
=
"true"
/>
</
out-parameters
>
<
actions
>
<
entity-make-value
entity-name
=
"Tutorial"
value-field
=
"tutorial"
/>
<
entity-set
value-field
=
"tutorial"
include
=
"all"
/>
<
if
condition
=
"!tutorial.tutorialId"
>
<
entity-sequenced-id-primary
value-field
=
"tutorial"
/>
</
if
>
<
entity-create
value-field
=
"tutorial"
/>
</
actions
>
</
service
>
|
Now to call the service instead of the implicit entity-auto one just change the transition to refer to this service:
1
2
3
4
|
<
transition
name
=
"createTutorial"
>
<
service-call
name
=
"tutorial.TutorialServices.create#Tutorial"
/>
<
default-response
url
=
"."
/>
</
transition
>
|
Note that the service name for a defined service like this is like a fully qualified Java class name. It has a "package", in this case "tutorial" which is the directory (possibly multiple directories separated by dots) under the component/service directory. Then there is a dot and the equivalent of the class name, in this case "TutorialServices" which is the name of the XML file the service is in, but without the .xml extension. After that is another dot, and then the service name with the verb and noun optionally separated by a hash (#).
Groovy 服务
你可以采用Groovy (也支持其他脚本语言)实现服务 来代替 XML 操作? 下面定义一个示例服务:
1
2
3
4
5
6
7
8
9
|
<
service
verb
=
"create"
noun
=
"Tutorial"
type
=
"script"
<
in-parameters
>
<
auto-parameters
include
=
"all"
/>
</
in-parameters
>
<
out-parameters
>
<
auto-parameters
include
=
"pk"
required
=
"true"
/>
</
out-parameters
>
</
service
>
|
Notice that the service.@type attribute has changed to script, and there is now a service.@locationattribute which specifies the location of the script.
Here is what the script would look like in that location:
1
2
3
4
|
def
tutorial = ec.entity.makeValue(
"Tutorial"
)
tutorial.setFields(context, true,
null
,
null
)
if
(!tutorial.tutorialId) tutorial.setSequencedIdPrimary()
tutorial.create()
|
当用 Groovy, 或者其他语言, 你可以用 Moqui Java API ,执行上下文 ExecutionContext class 在脚本中定义了变量名为 "ec". 关于API 的更多详细信息请看 API JavaDocs 以及 ExecutionContext 看具体的页面.
下一步?
现在你已经了解了Moqui Framework初步情况, 你可以阅读 (或者再次阅读) Moqui Framework简介的 PDF 文件,里面介绍框架的,一些思路,和如何工作. 该文件可以去 SourceForgehere 下载.
你也可以去读 Framework Features 的一些文档.