本章到目前为止已经介绍了Go模板引擎的不少特性,在继续了解更多特性之前,我们需要先学习一下如何在Web应用中使用布局。

所谓的布局(layout),指的是Web设计中可以重复应用在多个页面上的固定模式。为了构建协调一致的用户界面,Web应用常常需要展示一些相似的页面,因此Web应用也会经常用到布局。比如说,很多Web应用都拥有相应的头部菜单,以及提供服务器状态、版权声明、联系方式等附加信息的尾部栏,而其他一些Web应用可能会在屏幕的左侧提供导航栏又或者多级导航菜单。不难猜出,这些布局实际上都可以使用嵌套模板实现。

前面的小节曾经介绍过如何使用包含动作实现嵌套模板,但使用这种方法来开发复杂的Web应用,不仅需要将大量代码硬编码到处理器里面,还需要创建大量的模板文件,而引发这一问题的原因跟我们使用模板的方式有关。

正如之前所说,我们可以通过包含动作,在一个模板里面包含另一个模板:

{{ template "name" . }}

其中动作的参数name就是被包含模板的名字,并且这个名字还是一个字符串常量。

这意味着如果我们继续像之前一样,使用文件名作为模板名,那么因为每个页面都拥有它们各自的布局模板文件,所以程序最终将无法拥有任何可共用的公共布局,而这种做法跟构建布局的想法正好是相背的。比如说,对于代码清单5-22所示的模板文件,我们就不能把它用作公共的布局模板文件。

代码清单5-22 无效的模板布局文件

<html>  
    <head>    
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">    
        <title>Go Web Programming</title>  
    </head>  

    <body>    
        {{ template "content.html" }}  
    </body>

</html>

出现这种问题的根源在于我们实际上并没有以正确的方式使用Go模板引擎。尽管我们可以让每个模板文件都只定义一个模板,并将模板文件的名字用作模板的名字,但实际上,我们也可以通过定义动作(define action),在模板文件里面显式地定义模板,就像代码清单5-23所示的那样。

代码清单5-23 显式地定义一个模板

{{ define "layout" }}

<html>  
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">    
        <title>Go Web Programming</title>  
    </head>  

    <body>    
        {{ template "content" }}  
    </body>
</html>

{{ end }}

这个文件以一个{{ define "layout" }}标签作开头,并以一个{{ end }}标签结尾,而介于这两个标签之间的内容就是layout模板的定义。与此同时,通过使用另一个定义动作,我们还可以在这个文件里面再多创建一个模板。换句话说,我们可以像代码清单5-24所示的那样,在同一个模板文件里面定义多个不同的模板。

代码清单5-24 在一个模板文件里面定义多个模板

{{ define "layout" }}

<html>  
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">    
        <title>Go Web Programming</title>  
    </head>  

    <body>    
        {{ template "content" }}  
    </body>
</html>

{{ end }}

{{ define "content" }}

Hello World!

{{ end }}

代码清单5-25展示了处理器使用这些模板的方法。

代码清单5-25 使用显式定义的模板

func process(w http.ResponseWriter, r *http.Request) {    
    t, _ := template.ParseFiles("layout.html")    
    t.ExecuteTemplate(w, "layout", "")
}

分析模板的方法跟之前介绍过的一样,但是这次在执行模板的时候,程序需要显式地使用ExecuteTemplate方法,并把待执行的layout模板的名字用作方法的第二个参数。因为layout模板嵌套了content模板,所以程序只需要执行layout模板就可以在浏览器中得到content模板产生的Hello World!输出了。通过使用cURL获取模板输出的实际HTML文件,我们将看到以下结果:

> curl -i http://127.0.0.1:8080/process
HTTP/1.1 200 OK
Date: Sun, 08 Feb 2015 14:09:15 GMT
Content-Length: 187
Content-Type: text/html; charset=utf-8

<html>

    <head>    
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">    
        <title>Go Web Programming</title>  
    </head>  

    <body>
    Hello World!  
    </body>

</html>

用户除可以在同一个模板文件里面定义多个不同的模板之外,还可以在不同的模板文件里面定义同名的模板。作为例子,让我们首先移除layout.html文件中现有的content模板定义,然后分别在代码清单5-26和代码清单5-27所示的red_hello.html文件和blue_hello.html文件中重新定义content模板。

代码清单5-26 red_hello.html

{{ define "content" }}
<h1 style="color: red;">Hello World!</h1>
{{ end }}

代码清单5-27 blue_hello.html

{{ define "content" }}
<h1 style="color: blue;">Hello World!</h1>
{{ end }}

代码清单5-28展示了修改之后的处理器,它向我们演示了应该如何使用在不同模板文件中定义的两个content模板。

代码清单5-28 处理器使用在不同模板文件中定义的同名模板

func process(w http.ResponseWriter, r *http.Request) {    
    rand.Seed(time.Now().Unix())    
    var t *template.Template    
    if rand.Intn(10) > 5 {        
        t, _ = template.ParseFiles("layout.html", "red_hello.html")    
    } else {        
        t, _ = template.ParseFiles("layout.html", "blue_hello.html")    
    }    
    t.ExecuteTemplate(w, "layout", "")
}

这个处理器会根据生成的随机数,决定对red_hello.htmlblue_hello.html这两个模板文件中的哪一个进行语法分析。当处理器像之前一样执行包含了content模板的layout模板时,被随机选中的那个模板文件中定义的content模板就会被执行。因为red_hello.htmlblue_hello.html这两个模板文件都定义了content模板,所以它们中的哪一个被随机选中了进行语法分析,被分析文件中定义的content模板就会被执行。换句话说,我们可以在维持“layout模板包含content模板”这一关系不变的情况下,通过对不同的模板文件进行语法分析来达到改变输出结果的目的。

现在,如果我们重新编译并启动修改后的服务器,然后通过浏览器对其进行访问,那么我们将会随机看到蓝色或者红色的Hello World!输出,就像图5-12所示的那样。

图5-12 能够随机切换内容的模板

results matching ""

    No results matching ""