---
title: Otama模版系统
page_id: "070.020"
---

## Otama模版系统

Otama模版系统系统将界面逻辑从模版中完全分离出来. 它是专门为Treefrog框架设计的.
当配置文件(development.ini)按下面这样编辑后使用生成器生成程序骨架时, 将创建Otama系统的视图.

```
TemplateSystem=Otama
```

模版完全由HTML写成(使用.html作为后缀名). 一个"标记"用来标识逻辑代码应该插入的元素. 界面逻辑文件(.otm)由C++和标记写成. 当共享的视图(view)库构建时, 这些会被自动转换成C++代码.

<div class="img-center" markdown="1">

![视图(View)转换]({{ site.baseurl }}/assets/images/documentation/views_conv.png "View Convention")

</div>

基本上, 每个操作(action)会生成一对页面逻辑和模版. 文件名为[操作名(action name)].html和[操作名(action name)].otm(大小写敏感). 这些文件保存在"views/controller-name/"文件夹内.

一旦你创建了一个新模版, 为了将这些反馈到视图(view)的共享库中, 你需要在view目录下运行"make qmake".

```
$ cd views
$ make qmake
``` 

如果没有这样做, 检验在继续这一章时先阅读ERB章节, 因为两个模版系统有很多相同的地方. 还有, 关于Otama系统有很多内容要学习, 先学会ERB将更容易理解Otama.

## 输出字符串

我们将要输出字符串"Hello world.<br>"
在用HTML写的模版页, 使用一个叫data-tf的自定义属性, 然后为元素设置一个"标记(mark)". 属性的名字必须以"@"打头. 举例, 我们这样写:

```
<p data-tf="@hello"></p>
```

我们使用段落标签(\<p\> \</p\>)包住@hello标记.<br>
在标记中,只可以使用字母和下划线'_'. 不要使用其他字符.

接下来我们来看一些C++代码中的界面逻辑. 我们需要将C++代码和上面使用的b标记关联起来. 我们这样写:

```
@hello ~ eh("Hello world");
```

当构建的时候, 运行应用, 视图(view)即将会输出下面的结果:

```
<p>Hello world</p>
```

波浪号(~) 通过界面逻辑使用的标记连接C++代码表示"标记元素的内容被取代右侧内容取代". 记住eh()方法输出的值被传递了.

换句话说, 在P标签中的内容(这个例子为空)被"Hello world"替换了.data-tf属性将完全消失.

此外, 还有一个替代的方法输出同样的内容, 它也可以这样写:

```
@hello ~= "Hello world"
``` 

和ERB中一样, ~和eh()方法的组合可以被写成'~=', ~和echo()方法的组合可以写成'~=='.

虽然, 为了简单解释, 我们在这里输出了一个静态的字符串, 其实它可以用同样的方式输出一个变量. 当然, 它也可以输出从控制器(controller)传递的对象.

##### 概要: 在想要输出变量的地方放置标记(mark). 然后连接mark到代码.

## Otama操作符

夹在C++代码和标记(mark)找那个的符号叫做Otama操作符.

C++代码和元素用Otama操作符结合, 然后决定它们应该如何工作. 在界面逻辑, 要注意Otama操作符的两侧必须有一个空格.

这次, 我们使用一个不同的Otama操作符. 让我们假设逻辑界面是这样写的(冒号).

```
@hello : eh("Hello world");
```

视图的结果是这样的:

```
Hello world
```

P标签被删除了. 这是因为冒号用结果"更换整个标记(maked)的元素". 类似上面的内容, 这个也可以这样写:

```
@hello := "Hello world"
```

## 使用从控制器(controller)传递来的对象

为了显示从控制器(controller)导出的对象, 和ERB一样, 你可以用tfetch()宏或者T_FETCH宏获取后再使用它. 当msg可以导出为QString类型的变量时, 你可以这样写:

```
@hello : tfetch(QString, msg);  eh(msg);
```

和ERB一样, 获取(fetched)的对象被定义为局部变量.

通常, C++代码不适合写在一行. 要给一个标记(mark)写多行的C++代码, 可以像平常一样写但是要在每行后放置一个空行. 这个空行会被当成一个标记(mark)的一部分. 这样, 在一个标记(mark)和另外一个空行(包括只有一个空字的行)表现为界面逻辑的分割器.

##### 概要:逻辑通过一个空行进行界定.

接下来, 我们看看在两个不同的地方显示一个导出对象的例子. 在这个例子中, 如果你把它写陈#init, 它将首先被获取(fetched). 然后, 它可以在界面逻辑自由的使用.它应该看起来类似这样:

```
#init : tfetch(QString, msg);
@foo1 := msg
@foo2 ~= QString("message is ") + msg
```

像说过的那样, 要导出引用多次的对象, 在*#init*中进行获取(fetch).

这里是另外一种方法导出对象.<br>
在Otama操作符后面放置"$". 例如, 你可以写成下面这样导出名为*obj1*的对象.

```
@foo1 :=$ obj1
```

它表示obj1对象fetch()处理后, 使用eh()输出值. 然而, 这个处理过程等于获取(fetch)处理, 局部的变量事实上没有被定义.

获得使用echo()方法的输出, 可以这样写:
```
@foo1 :==$ obj1
```

和ERB中一样.

##### 概要:要导出对象,使用 =$ 或者~$.

## 循环

接下来, 我将接收如何使用循环重复显示列表的数字.<br>
在模版中, 我们这样写.

```
< tr data- tf="@ foreach"> 
  < td data- tf="@ id"></ td>
  < td data- tf="@ title"></ td>
  < td data- tf="@ body"></ td>
</tr>
```

那些是名为blogList的Blog类的列表导出的对象. 我们像给for语句写一个循环. 这个while语句也是非常类似的.

```
@foreach :
tfetch(QList<Blog>, blogList);    /* 获取(fetch)处理 */
for (auto &b, blogList) {
     %%
}

@id ~= b.id()

@title ~= b.title()

@body ~= b.body()
```

%%标记是重要的, 因为它代表标记的整个元素(*@foreach*). 换句话说, 这个例子中, 它代表从\<tr\>到\</ tr\>的元素. 因此, 通过重复\<tr\>标签, 使用*@id*, *@title*, 和*@body*,设置每个内容元素的值的foreach语句在视图(view)中那个输出这样的结果:

```
< tr>
  <td>100</td>
  <td>Hello</td>
  <td>Hello world!</td>
</tr><tr>
  <td>101</td>
  <td>Good morning</td>
  <td>This morning ...</td>
</tr><tr>
    :    (<-重复输出列表的对象)
```

和前面的一样, data-tf属性将会消失.

## 增加一个属性

让我们使用Otama操作符给一个元素增加属性.<br>
假设你的模版找那个有这样的标记(mark):

```
<span data-tf="@spancolor">Message</span>
```

现在, 假设你在界面逻辑中这样写:

```
@spancolor + echo("class=\"c1\" title=\"foo\"");
```

下面是输出的结果:

```
<span class="c1" title="foo">Message</span>
```

通过使用+操作符, 你可以增加属性.<br>

顺便提一句, 不可以使用eh()方法代替echo()方法, 因为双引号被转义后会是一个不同的意义.

在界面逻辑中, 我们还可以用另外的一个方法写成这样:

```
@spancolor +== "class=\"c1\" title=\"foo\""
```

echo()方法可以重写成'=='.

此外, 还可以写成这样,下面的替代方法输出同样的结果:

```
@spancolor +== a("class", "c1") | a("title", "foo")
```

a()方法创建一个表示HTML属性的THtmlAttribute对象, 使用\|(竖线)来连接它们. 连接后它不是一个THtmlAttribute对象, 但是如果你用echo()方法输出, 它们会被转换成*key1="val1", key2="val2" ...*这样的字符串, 作为结果添加到属性中.
你愿意的话还可以添加更多属性.

## 重写\<a\>标签

可以使用冒号':'操作符重写\<a\>标签. 它像上面描述的一样.<br>
回顾一下, \<a\>标签在模版中是这样标记(marked)的.

```
<a class="c1" data-tf="@foo">Back</a>
```

举例, 我们可以这样重写视图(view)(Blog的视图)的界面逻辑:

```
@foo :== linkTo("Back", urla("index"))
```

视图(view)输出下面的结果:

```
<a href="/Blog/index/">Back</a>
```

因为linkTo()方法生成一个\<a\>标签, 我们可以得到这结果. 不幸, 类原来的属性已经消失了. 原因就是这个操作符有替换整个元素的效果.

如果你想设置属性, 你可以增加一个参数到linkTo()方法.

```
@foo :== linkTo("Back", urla("index"), Tf::Get, "", a("class", "c1"))
```

类属性将会和上面的结果一样被输出.

虽然属性信息可以被输出, 你肯定不会像在界面逻辑中麻烦地重写这些信息.<br> 
这里有个解决方法\|==操作符. 它有合并标签内容的同时保留属性的效果.

所以, 让我们这样重写界面逻辑:

```
@foo  |== linkTo("Back", urla("index"))
```

视图(view)输出下面的结果:

```
<a class="c1" href="/Blog/index/">Back</a>
```

原来的类属性保留下来的, 没有消失.

\|== 操作符是有条件地合并元素.条件是元素必须是相同的标签.此外, 两者带有相同的属性, 界面逻辑的值优先级更高.

通过使用这个操作符, 设计的信息(HTML属性)可以被转移到模版.

##### 概要: 使用\|==操作符保留模版设计的属性然后再合并.

**说明:**<br>
只有\|==操作符才可用, '\|'和'\|='都不能工作.

## 表单标签

不要直接使用表单标签\<form\>来Post数据, 除非你已经打开跨域访问伪造(CSRF)监测. 只有在模版中写表单标签, 才能接收Post数据. 我们需要用隐藏的参数来嵌入秘密的信息.

我们在模版中使用\<form\>标签. 将标记(mark)放置在模版的\<form\>标签后, 然后用formTag()方法的输出作为内容进行合并.

模版:

```
   :
<form method="post" data-tf="@form">
   :
```

界面逻辑:

```
@form |== formTag( ... )
```

你将可以正常Post数据.

关于打开跨域访问伪造(CSRF)监测和关于安全的更多信息, 请查看[安全]({{ site.baseurl }}/ch/user-guide/security/index.html){:target="_blank"}章节.

## 擦除元素

如果你在模版中标记*@dummy*元素, 它不会在视图中输出.假设你的模版这样写.

```
<div>
<p>Hello </p>
<p data-tf="@dummy">message ..</p>
</div>
``` 

然后, 视图(view)将输出下面的结果:

```
<div>
<p>Hello </p>
</div>
```

## 擦除标签

你可以仅保留内容但是删除起始标签和结束标签.<br>

举例, 当使用布局(layout)时, \<html\>标签会通过布局(laytout)文件输出, 所以你不再需要在模版中输出, 而你希望布局(layout)基于HTML, 而同时将\<html\>标签保留在模版中.<br>

假设你的模版这样写.

```
<html data-tf="@dummytag">
<p>Hello </p>
</ html>
```

然后, 视图(view)将输出下面的结果:

```
<p>Hello </p>
```

当你希望在网页设计时保留, 但是在视图中擦除时可以使用它.

## 包含头文件

我们已经说过关于界面逻辑模版转换到C++代码. 头文件和用户定义的文件不会自动包含, 必须自己添加它们. 不过基本的Treefrog头文件会被包含.

举例, 如果你希望包含*user.h*和*blog.h*文件, 你应该在界面逻辑的顶部这样写:

```
#include "blog.h"
#include "user.h"
```

所有的都和C++代码一样!<br>
以#include开头的字符串自己转移到视图(view)的代码中.

## Otama操作符

下面的表描述了我们已经讨论过的Otama操作符.

<div class="table-div" markdown="1">
| 操作符Operator | 描述Description                                                                                                             | 说明Remarks                  |
|----------------|-----------------------------------------------------------------------------------------------------------------------------|------------------------------|
| :              | **元素替换**<br>被标记的元素和子元素会完全被操作符右侧的eh()方法或者echo()方法输出的内容替换.                               |%%表示元素本身可以被替换      |
| ~              | **内容替换**<br>被标记的元素的内容会被操作符右侧的eh()方法或者echo()方法输出的内容替换.                                     |                              |
| +              | **属性增加**<br>如果想更已经标记的元素增加一个属性, 使用这个操作符加上操作符右侧的echo()方法输出的一个字符串.               | += 是HTML转义,可能用得不多.  |
| \|==           | **元素合并**<br>Based on the marked elements, the specified strings will be merged on the right-hand side of this operator. | '\|' 和'\|=' 不可以使用.     |

</div><br>

这4个操作符的扩展版本在下面.
不再需要echo()语句和eh()语句后, 代码能够写得更短.

<div class="table-div" markdown="1">

| 操作符Operator              |描述 Description                                                                                                              |
|-----------------------------|------------------------------------------------------------------------------------------------------------------------------|
| :=<br> :==<br> :=$<br> :==$ |元素被HTML转义的变量替换.<br> 元素被变量替换.<br>元素被HTML转义的导出的对象替换.<br>元素被导出的对象替换.                     |
| ~=<br> ~==<br> ~=$<br> ~==$ | 用HTML转义的变量替换内容.<br> 用变量替换内容.<br>  用HTML转义的导出对象替换内容.<br> 用导出的对象替换内容.                   |
| +=<br> +==<br> +=$<br> +==$ |增加一个HTML转义的变量到属性.<br> 增加一个变量到属性.<br>  增加一个HTML转义的导出对象到属性.<br> 增加一个导出的对象到属性.    |
| \|==$                       | 用导出的对象合并元素.                                                                                                        |
</div><br>

## 注释

如果你希望在界面逻辑中写注释, 请将注释写在/*...*/中.

```
@foo ~= bar    /*  这里是注释 */
```

**说明:** C++ 使用"// .." 但是这个不能用在界面逻辑中.