大大催了,得赶紧写,这一系列大概还要五六篇。
在前一篇中定义了我定义了Shark中标签的通用结构,它有两种形态,主要区别在参数上。
结构1:涉及到参数的传递,形如:
@mytag(type1 var1: desc1, type2 var2: desc2) {
body
}
结构2:对一段代码进行操作,如对值进行判断等,形如:
@mytag(some dart code) {
body
}
注意参数块`(...)`和主体块`{...}`都是可选的,但至少要提供一个。
我们可能会有一个疑问:使用这两种结构,是否可以设计出优雅好用的标签?
为了回答这个问题,我找了一些在模板引擎中常用的标签,按上面两种结构来定义,以验证可行性。
## 常用标签
流程控制:
if
elseif
else
for
模板交互:
params
extends
renderBody
render
文本内嵌:
dart
plainText
上面这些基本的标签,再加上表达式:
@simpleVar
@{complexVar.more.more()}
基本上就可以满足各常用的需求了。如果有更复杂的需求,我们还可以有两种做法:
使用dart标签,内嵌一些dart代码,如函数定义等。Shark并不禁止在模板中嵌入代码,该出手时就出手
自定义标签。注意上面的这些标签对于模板来说,都不是侵入式的,我们甚至可以用自己的实现替代它们。Shark将提供强大的自定义标签功能,让我们实现复杂的功能
下面我们将从用户角度考虑这些标签的设计,先不管它们如何实现,甚至是否能实现。
功能不用多说,它可以用结构2实现:
@if(name=='Shark') {
<div>Hello, @name</div>
}
@if(name.startsWith(“thought”)) {
<div>@{name}works</div>
}
@if (names.where((name) => name.startsWith(“X”)).isNotEmpty) {
<div>There is at least one name starts with X</div>
}
它基本上跟@if一模一样:
@if(num==1) {
...
} @elseif(num==2) {
...
}
这个不需要参数,最简单
@if(ok) {
...
} @else {
...
}
在很多类似的模板引擎中,对于这种情况,else
前面的@
是不需要的:
@if(ok) { … } else { … }
看起来更简洁一些。但对于Shark来说,稍麻烦一点,但也不是很难接受。
for
在dart中的语法形如:
for(int i=0; i<10; i++) { ... }
for(var name in names) { … }
虽然照搬也可以,但如果采用类似于java的形式,可能更简单一些:
@for(name: names) {
...
}
index
哪儿去了?在这里,我参考了play中的模板语法,将会在body内生成一些特别的变量,提供更丰富的信息:
@for(name: names) {
Following varaibles are valid in the body:
- @name_index
- @name_isFirst
- @name_isLast
- @name_isOdd
- @name_isEven
}
这样就是一个功能超强的for了。
还要再为它增加一个自动添加分隔符的功能,这在生成JSON数据时特别有用:
[
@for(name: names, separator: ',') {
{ “name”: “@name” }
}
]
假如names
值为['Shark', 'Bird', 'Chicken']
,它将会生成:
[
{ “name”: “Shark” },
{ “name”: “Bird” },
{ “name”: “Chicken” }
]
注意它会在两个元素之间自动添加上指定的separator,不多不少。
对了,for
还可以跟else
搭配:
@for(user: users) {
} @else {
}
这个标签一般放在页首,用来指定当前模板可以传入哪些参数,比如:
user.shark
@params(String name, String blog) {
<div>User: @name, blog: @blog</div>
}
如果我们要写很长的html,还可以选择忽略{}
符号:
@params(String name, String blog)
由于模板文件user.shark
将被编译为user.dart
,则我们可以这样调用它:
import “user.dart” as view$user;
showUser(User user) {
return view$user.render(name: user.name, blog: user.blog);
}
对于网站来说,将页面划分为布局页和内容页是很常用的需求,所以我们会提供extends
标签实现这个功能。
布局页:layout.shark
<div>@renderBody()</div>
内容页: index.shark
@extends(layout) {
<div>main content of index page</div>
}
这样在渲染index模板时,它会自动把它的内容嵌入到layout.shark中的@renderBody()
处。
同@params
一样,@extends
的{}
也可以省略:
@extends(layout)
并且还可以向layout.shark中传参数:
layout.shark
@params(String pageTitle)
<title>@pageTitle</title>
index.shark
@extends(layout, pageTitle: 'Index page, welcome')
标签参数中的paramDescription位置,实际上可以传入多种类型的参数:
另一个变量名
以单引号括起来的字符串
以双引号括起来的字符串
以{}
括起来的一块内容
这么规定,是考虑到有时候我们需要向另一个标签中传入丰富的内容,比如一个复杂的extends:
@extends(layout,
name: someName,
pageTitle: 'Index page',
pageCopyright: “Copyright”,
moreJs: {
<script src="..."></script>
<script src="..."></script>
}
)
这个标签在前面已经出现过,它是个占位符。如果有另一个页面继承或调用它所在的模板,则会用另一个模板的内容填充它。它实际上不需要任何额外信息,但为了表示它是一个标签,所以我们会在后面加个()
layout.shark
有时候我们想把一个大页面分解为多个小页面,以获得更清晰的结构;有时候我们想定义一些小页面,在多个大页面中复用,这时候可以使用render
标签。
先定义两个小页面:
left.shark
@param(List
big.shark
然后在大页面中调用:
@!dart {
var operations = ['add', 'delete', 'update'];
}
@render(left, operations: operations)
@render(big) {
More content
}
有时候我们需要在页面中嵌入一些dart代码,实现一些复杂的功能,如定义一个函数,初始化一些数据等,这时可使用dart
标签。
@!dart {
here is all kinds of dart code
}
由于主体中的内容是dart代码,一般应该当成纯文本看待,不应该对其内容进行任何解析或转义,所以我们通常会有@
后加上!
,用来告诉解析器,请把{}
中的代码当作纯文本看待。
同时,由于{}
中内容为纯文本,它可以包含任意字符,所以很可能出现一个}
,让{}
提早结束。为了避免这种情况,我们可以重复{
和}
,只要它们个数相等:
@!dart {{{
hello() { world() {
}}
}}}
被dart标签包起来的内容,将会当作dart代码嵌入到生成的dart文件中。
有时候只想把一大段内容原封不动的嵌入到最终生成的文本中,这时候可以使用plainText标签。
@!plainText(trim: true) {
A lot of text here
}
加上trim参数,可以控制是否把文本两边的空白去掉,默认为false
通过上面对各常用标签的解释,可以认为开头定义的两种标签通用结构是可行的,下一篇我们将利用PetitParser来解析它。而这些常用标签的实现过程,将在后面与编译相关的内容中实现。
PS:
另外,写到最后,有没有发现这简直就是SharkDart的使用教程?没错,对于开发者来说,这是设计,但对用户来说,就和教程差不多了。