跳转至

Leaf是Vapor框架中使用的一种模板语言。模板语言允许传递信息给页面,然后根据所传递的页面信息来动态的生成最终的HTML。模板语言也为我们开发过程中节约代码提供了方法,我们可以使用模板语言来写页面展示代码,其中可以加入各种参数变量,不同页面间也可以复用之前写好的代码块。个性页面显示方式时,有时只需要更改一处地方就可以改变所有页面的显示方式了。我们可以在一个模板中嵌套另一个模板。

我们在之前开发的后端API项目上直接开发Web页面。需要在项目的Package.swift文件中添加依赖:

Package.swift

1
2
3
4
...
.package(url: "https://github.com/vapor/leaf.git", from: "3.0.1")
...
.target(name: "App", dependencies: ["FluentPostgreSQL", "Vapor", "Leaf"]),

Leaf模板引擎默认使用目录Resources/Views, 所以我们新建一个同名目录来存放相关代码数据和文件。 mkdir -p Resources/Views

新建一个专门用来返回Web页面的router控制器WebsiteController.swift

WebsiteController.swift

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import Vapor
import Leaf

struct WebsiteController: RouteCollection {

    func boot(router: Router) throws {
        router.get(use: indexHandler)
    }

    func indexHandler(_ req: Request) throws -> Future<View> {
        return try req.view().render("index")
    }
}

然后在routes.swift中注册控制器,使用生效:

1
2
3
4
...
let websiteController = WebsiteController()
try router.register(collection: websiteController)
...

还要把Leaf作为一种服务进行配置: configure.swift

1
2
try services.register(LeafProvider())
config.prefer(LeafRenderer.self, for: ViewRenderer.self)

leaf index

注入参数

Leaf中使用#(arg)的方式注入参数到页面模板中。在Vapor中很多都使用了Codable协议,Leaf也不例外

index.leaf

1
2
3
...
<title>#(title) | Acronyms</title>
...

WebsiteController.swift

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import Vapor
import Leaf

struct IndexContext: Encodable {
    let title: String
}

struct WebsiteController: RouteCollection {

    func boot(router: Router) throws {
        router.get(use: indexHandler)
    }

    func indexHandler(_ req: Request) throws -> Future<View> {
        let context = IndexContext(title: "Homepage")
        return try req.view().render("index", context)
    }
}

注入参数

展示缩略语

index.leaf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>#(title) | Acronyms</title>
    </head>
    <body>
        <h1>Acronyms</h1>
        #if(acronyms) {
        <table>
            <thead>
                <tr>
                    <th>Short</th>
                    <th>Long</th>
                </tr>
            </thead>
            <tbody>
                #for(acronym in acronyms) {
                <tr>
                    <td>#(acronym.short)</td>
                    <td>#(acronym.long)</td>
                    }
            </tbody>
        </table>
        } else {
            <h2>There aren't any acronyms yet!</h2>
        }
    </body>
</html>

WebsiteController.wift

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Vapor
import Leaf

struct IndexContext: Encodable {
    let title: String
    let acronyms: [Acronym]?
}

struct WebsiteController: RouteCollection {

    func boot(router: Router) throws {
        router.get(use: indexHandler)
    }

    func indexHandler(_ req: Request) throws -> Future<View> {
        return Acronym.query(on: req).all()
            .flatMap(to: View.self) { acronyms in
                let acronymsData = acronyms.isEmpty ? nil : acronyms
                let context = IndexContext(title: "Homepage", acronyms: acronymsData)
                   return try req.view().render("index", context)
        }
    }
}

leaf-tags

跳转Acronyms详情

index.leaf

1
2
3
...
<td><a href="/acronyms/#(acronym.id)">#(acronym.short)</a></td>
...

acronyms.leaf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>#(title) | Acronyms</title>
    </head>
    <body>
        <h1>#(acronym.short)</h1>
        <h2>#(acronym.long)</h2>
        <p>Created by #(user.name)</p>
    </body>
</html>

WebsiteController.swift

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
...
struct AcronymContext: Encodable {
    let title: String
    let acronym: Acronym
    let user: User
}
...
        router.get("acronyms", Acronym.parameter, use: acronymHandler)
...
    func acronymHandler(_ req: Request) throws -> Future<View> {
        return try req.parameters.next(Acronym.self)
            .flatMap(to: View.self) { acronym in
                return acronym.user.get(on: req)
                    .flatMap(to: View.self) { user in
                        let context = AcronymContext(title: acronym.short, acronym: acronym, user: user)
                        return try req.view().render("acronym", context)
                }
        }
    }

index-leaf-link

acronyms-leaf