Vapor(六)视图篇

  1. TemplateKit
  2. Leaf
    1. 设置
    2. 文件夹结构
    3. 渲染
    4. 语法

Vapor里的视图主要是通过Leaf实现的,Leaf是一种简单的模板语言,通过模板来实现在HTML中的动态Web,就是在Leaf模板中通过规定的语法来渲染出对应的内容。Leaf有以下目标

  • Small set of strictly enforced rules 小套严格执行规则
  • Consistency 一致性
  • Parser first mentality 解析器第一心态
  • Extensibility 可扩展性

而动态Web,则是指在页面里嵌套了程序,这种网站对一些框架相同,更新较快的信息页面进行内容与形式的分离,将信息内容以记录的形式存入了网站的数据库中,以便于网站各处调用。这样,我们看到的一个页面可能在服务器上并不一一对应某个 html 的文件,网页框架里也套了很多数据库里记录的内容。

TemplateKit

TemplateKit是为了定义通用的模板结构和序列处理的模板语言能更容易被实现而设计的。

Leaf使用TemplateKit来渲染视图,通过遵从TemplateParser协议的解析器LeafParser,将leaf解析到抽象的语法树(AST)上

//greeting.leaf -> LeafParser -> AST

//leaf
Hello, #capitalize(name)!

//parser
func parse(scanner: TemplateByteScanner) throws -> [TemplateSyntax]

//AST
[
    .raw(data: "Hello. "), 
    .tag(
        name: "capitalize", 
        parameters: [.identifier("name")]
    ),
    .raw(data: "!"), 
]

然后通过遵从TemplateRendererLeafRenderer填充AST中的变量,最后渲染出视图View

//AST + Data -> TemplateSerializer -> View

//填充AST中的变量
let data = TemplateData.dictionary(["name": "vapor"])

//render
public func render(_ path: String, _ context: TemplateData) -> Future<View>

//View
Hello, Vapor!

小结整个渲染的流程就是

                            LeafRenderer
                                 |
|----------------------------------------------------------------|
 greeting.leaf -> LeafParser -> AST -> TemplateSerializer -> View
                                 ^
                                /
                   TemplateData

Leaf

Leaf是一种使用Swift思维语法的模板语言。使用它能够为前端产生动态的HTML页面,或生产通过API发送的富邮件。

设置

//In SPM
let package = Package(
    name: "MyApp",
    dependencies: [
        .package(url: "https://github.com/vapor/leaf.git", from: "3.0.0"),
    ],
    targets: [
        .target(name: "App", dependencies: ["Leaf", ...])
    ]
)

//In configure.swift
import Leaf
try services.register(LeafProvider())
config.prefer(LeafRenderer.self, for: ViewRenderer.self) //解决有多个视图渲染器时的冲突,选择优先使用者。

文件夹结构

Leaf expects the views folder to be a ./Resources/Viewsrelative to your project’s root.

Leaf需要放在下面的结构的Views文件夹中,并且路径也需按此规划。

VaporApp
├── Package.swift
├── Resources
│   ├── Views
│   │   └── hello.leaf
├── Public
│   ├── images (images resources)
│   ├── styles (css resources)
└── Sources
    └── ...

渲染

//In leaf
Hello, #(name)!

//In routes.swift
import Leaf
router.get("hello") { req -> Future<View> in
    return try req.view().render("hello", ["name": "Leaf"])
}

语法

Leaf内建tag的用法

#(variable)
#embed("template")
#set("title") { Welcome to Vapor }
#count(friends)
#for(friend in friends) { <li>#(friend.name)</li> }

上下文

只要实现了Encodable,都可以传值给Leaf

struct WelcomeContext: Encodable {
    var title: String
    var number: Int
}
return try req.view().make("home", WelcomeContext(title: "Hello!", number: 42))

//In leaf
<h1>#(title)</h1>
<p>#(number)</p>

条件

//In leaf

#if(title) {
    The title is #(title)
} else {
    No title was provided.
}

#if(title == "Welcome") {
    This is a friendly web page.
} else {
    No strangers allowed!
}

#if(lowercase(title) == "welcome") {
    This is a friendly web page.
} else {
    No strangers allowed!
}

#if(title == "Welcome") {
    This is a friendly web page.
} else if (1 == 2) {
    What?
} else {
    No strangers allowed!
}

循环

struct SolarSystem: Codable {
    let planets = ["Venus", "Earth", "Mars"]
}

return try req.view().render(..., SolarSystem())

//In leaf
Planets:
<ul>
#for(planet in planets) {
    <li>#(planet)</li>
}
</ul>

#for(planet in planets) {
    #if(isFirst) { #(planet) is first! } //isFirst、isLast、index
}

嵌套

//In child.leaf
#set("body") {
    <p>Welcome to Vapor!</p>
}

#embed("master") //嵌套了 master.leaf 的内容到这里

//In master.leaf
<html>
    <head>
        <title>#(title)</title>
    </head>
    <body>#get(body)</body>
</html>

//In context
["title": "Hi there!"]

//In View
<html>
    <head>
        <title>Hi there!</title>
    </head>
    <body><p>Welcome to Vapor!</p></body>
</html>

注释

#// Say hello to the user
Hello, #(name)!

#/*
     Say hello to the user
*/
Hello, #(name)!

日期

render(..., ["now": Date()])

//In leaf
The time is #date(now)
The date is #date(now, "yyyy-MM-dd")

大小写

#capitalize(name) //首字母大写
#uppercase(name)
#lowercase(name)

包含

//#contains
#if(contains(planets, "Earth")) {
    Earth is here!
} else {
    Earth is not in this array.
}

计数

Your search matched #count(matches) pages. //返回数组的元素个数

自定义标签

实现TagRenderer

final class NowTag: TagRenderer {
    init() { }

	func render(tag: TagContext) throws -> 	EventLoopFuture<TemplateData> {
		let formatter = DateFormatter()
		switch tag.parameters.count {
			case 0: formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
			case 1:
				guard let string = tag.parameters[0].string else {
	  				throw ...
				}
				formatter.dateFormat = string
			default:
				throw ...
		}
		let string = formatter.string(from: .init())
		return tag.container.future(.string(string))
	}
}

配置标签

services.register { container -> LeafTagConfig in
    var config = LeafTagConfig.default()
    config.use(NowTag(), as: "now")
    return config
}

使用效果

The time is #now()

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 mingfungliu@gmail.com

文章标题:Vapor(六)视图篇

文章字数:1.2k

本文作者:Mingfung

发布时间:2018-08-20, 21:56:00

最后更新:2018-08-28, 22:19:36

原始链接:http://blog.ifungfay.com/后端/Vapor(六)视图篇/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏

宝贝回家