对于web
程序,视图层要完成的任务就是生成HTML
页面代码,然后返回给浏览器。对开发人员来说,就是编写HTML
模板文件,它有大量的占位符性质的元素可以根据从控制器传过来的数据自动“翻译”为原生的HTML
元素,最后将它们组合起来就是最终返回到客户端的HTML
页面代码。
对于TreeFrog
的视图层,可以从ERB
和Otama
两个模板系统中选择一个使用。它们都支持变量动态取值,条件流程控制和循环流程控制。它们两者都没有什么运行性能上的差异,因为它们都可以转化为C++
代码然后再编译到一个共同的动态库中。
1.文件路径
一开始,ERB
只是一个用于将Ruby
脚本代码嵌入到网页页面中的库,其特征语法格式如下:<%...%>
。同样地,我们也可以在<%...%>
中编写C++代码
。一般情况下,ERB
模板文件的路径遵守以下规则:views/控制器名/动作名.erb
。
2.动作与视图之间的关系
当在Foo控制器
中的bar动作
中执行render()
函数(不带参数),那么将输出views/foo/bar.erb
文件的内容给浏览器。如果render()
函数带一个参数,则返回以该参数命名的erb文件
给浏览器。当然你可以自由地添加模板文件,但请记得随时编译更新
$ cd views
$ make qmake
3.输出字符串
按照程序界的惯例,如果我们想输出Hello world
,erb
提供给我们两种方法:
<% eh("Hello world"); %>
这里的eh()
方法就是将字符串进行HTML转义
(避免跨站脚本攻击)后再输出,如果不需要HTML转义
,那么可以调用echo()
方法。
另一种方法就是使用<%=...%>
标记,这与使用eh()
的效果一样。
<%= "Hello world" %>
同样地,如果你不想进行HTML转义
,那么可以使用<%==...%>
,这与使用echo()
的效果一样。
<%== "<p>Hello world</p>" %>
4.设置变量输出的默认值
如果想要输出的变量是一个空串,可以转而输出一个预先设置的默认值。
<%= str %|% "none" %>
5.使用从控制器传过来的对象
为了使用从控制器传过来(一般是使用texport()
方法来导出变量)的对象,要先调用tfetch(变量类型,变量名)
或者T_FETCH
宏定义来声明要使用的导出变量。
<% tfetch(Blog, blog); %>
<%= blog.title %> ←输出博客文章的标题
调用了tfetch
函数之后的变量在此后可以随时使用,相当于一个属于该模板文件的局部变量。而且tfetch
同一个变量两次可能会造成报错——相当于变量重复定义。
如果导出变量是int
或者QString
类型,那么你可以直接使用tehex()
函数:
<% tehex(foo); %>
你可以这么理解:tehex()
就是eh()
和tfetch()
函数的结合,区别在于你可以不用担心重复定义变量而造成报错(即可以多次对同一个变量调用tehex()
函数)。如果你不想进行HTML转义
,那么可以选择techoex()
函数,可认为是echo()
和tfetch()
函数的结合。
顺带一提,其实还有另一种输出变量的方法,那就是使用<%=$ 变量名 %>
,它相当于<% tehex(变量名);%>
;当然对应于不转义的techoex()
函数,也有<%==$ 变量名 %>
的表示方式。
6.如何编写注释
这部分不会对最终生成的网页代码有任何影响,但是它会保留在转换之后的C++
代码(保存在views/_src
目录中)中。写法如下:
<%# 注释区 %>
7.导入其他文件
如果你想在ERB
模板文件中使用一个类(例如某一个模型类),你需要导入这个头文件:
<%#include "blog.h" %>
因为你在<%...%>
中编写是C++
代码,随意无可避免地要导入对应的头文件,这些都将在编译的时候进行。
8.循环体
比如,我们想提取blogList
这个博客列表中的每一个Blog
对象,我们可以这么写(记得要事先通过tfetch
获取这个变量):
<% QListIterator<Blog> i(blogList);
while ( i.hasNext() ) {
const Blog &b = i.next(); %>
................
<% } %>
或者,直接利用QT
的foreach()
方法:
<% foreach (Blog b, blogList) { %>
................
<% } %>
9.生成超链接
为了生成超链接<a>
标记,我们可以使用linkTo()
方法:
<%== linkTo("Back", QUrl("/Blog/index")) %>
↓生成
<a href="/Blog/index">Back</a>
我们也可以通过url(控制器名, 动作名)
方法来构造URL
,比如:
<%== linkTo("Back", url("Blog", "index")) %>
↓生成
<a href="/Blog/index/">Back</a>
如果目标动作同属于一个控制器,那么可以使用urla()
方法,只用声明一个参数即可:
<%== linkTo("Back", urla("index")) %>
↓生成
<a href="/Blog/index/">Back</a>
如果要实现这样的效果:用户点击该链接时弹出一个确认对话框,用户点击确认后才实现真正的跳转。(Tf::Post
指定请求方法为Post
)
<%== linkTo(tr("Delete"), urla("remove", 1), Tf::Post, "confirm('Are you sure?')") %>
↓生成
<a href="/Blog/remove/1/" onclick="if (confirm('Are you sure?')) {
var f = document.createElement('form');
: (omission)
f.submit();
} return false;">Delete</a>
如果我们想往网页标签中添加属性,可以使用THtmlAttribute(属性名,属性值)
方法:
<%== linkTo("Back", urla("index"), Tf::Get, "", THtmlAttribute("class", "menu")) %>
↓生成
<a href="/Blog/index/" class="menu">Back</a>
你也可以将THtmlAttribute()
方法简写为a()
方法:
<%== linkTo("Back", urla("index"), Tf::Get, "", a("class", "menu")) %>
当有多对属性需要设置时,可以使用|
运算符:
a("class", "menu") | a("title", "hello")
↓生成
class="menu" title="hello"
10.生成表单
浏览器经常通过pos
t将<form>
表单数据传输到服务器,这里我们也可以调用formTag()
函数来指定要触发该控制器下的哪个动作从而生成对应的表单,进而实现这一过程。
<%== formTag(urla("create"), Tf::Post) %>
...
...
</form>
当然,你会认为其实这么简单的过程完全可以通过逐个拼接字符串来完成,但是通过这种方法生成的表单可以进行跨站请求伪造(CSRF)的检测识别,更加安全。
我们可以通过配置application.ini
文件来开启或关闭跨站请求伪造(CSRF
)的检测功能:
EnableCsrfProtectionModule=true
11.Layout布局
所谓布局,就是该网站各个页面都共用的特殊模板(比如:头部导航栏、菜单栏、底部的友情链接),它一般保存在view/layouts
目录下。
Layout
的匹配规则/优先级:
- 该动作
- 该控制器
- 默认布局
- 不使用
Layout
一般情况下,只会使用一个Layout
,根据优先级优先匹配。下面以一个布局文件为例子说明布局文件和其他一般模板文件的关系。
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Blog Title</title>
</head>
...
<%== yield() %>
...
</html>
最重要的部分——<%== yield(); %>
,就是要嵌入一般模板文件的地方。换句话说,当控制器调用render()
方法时,模板文件输出的内容将合并到布局文件的这个地方,然后最终生成返回给浏览器的网页文件。
11.1.指定布局文件
当我们想指定布局,而不是按优先级匹配时,可以在render()
方法中添加第二个参数。比如,我们想使用simplelayout.erb
这个模板文件:
render("show", "simplelayout");
11.2.设置控制器的默认布局文件
在控制器的构造方法中,我们可以通过调用setLayout()
方法来指定默认的布局文件:
setLayout("basiclayout"); //指定默认布局文件为basiclayout.erb
11.3完全不使用布局文件
如果控制器没有指定默认布局文件,那么将统一使用application.erb
这个布局文件,当然我们也可以通过在一个动作中调用setLayoutDisabled(true)
来实现完全不使用布局文件。
12.局部模板
如果你浏览过网站,那么肯定会发现有一些模块也经常出现在各个不同的页面中,但是它们呈现出来的更像是一个网页插件,而不是一个基础框架。比如广告悬浮窗口、某些工具栏。
为了实现这种效果,我们可以使用局部模板,它保存在views/partial
目录下,我们可以通过调用renderPartial(局部模板名)
方法来使用它
<%== renderPartial("content") %>//将content.erb嵌入到原始页面中