3

Revel是怎样实现渲染变量到模板中的?

控制器admin2049 次浏览

在添加控制器Action的时候,需要传递一些变量到模板中,这些变量被Revel收集到go程序的入口函数app/tmp/main.go中。程序运行时,Revel通过堆栈中的函数调用信息获取一个行号,然后使用行号取回变量名称,并将变量赋值后带到模板中去。

1、比如goreve的代码:app/controllers/topic.go

type Topic struct {
	Application
}

func (c Topic) New() revel.Result {
	title := "发表新帖"

	return c.Render(title)
}

2、当保存代码时,Revel会重新整理代码,收集控制器相关信息保存到app/tmp/main.go中

revel.RegisterController((*controllers.Topic)(nil),
	[]*revel.MethodType{
		
	...
	})

注册Topic控制器的入口中包含了一个[]*revel.MethodType,这个切片收集了控制器所有的Action(一个导出函数并返回revel.Result类型)。

 

3、对应于Topic.New这个Action,Revel生成如下的代码,包含3个参数,Name、Args和RenderArgNames,分别是Action名称、Action参数和模板变量名称。

&revel.MethodType{
	Name: "New",
	Args: []*revel.MethodArg{ 
	},
	RenderArgNames: map[int][]string{ 
		22: []string{ 
			"title",
		},
	},
},

RenderArgNames就是传递到模板中的变量名。它是一个key为22,value为[]string的一个map,22代表行号,也就是c.Render(title)函数调用时所在的代码行,后面会用到。

4、下面来看看c.Render()这个函数做了什么,他是怎么通过行号取回参数的。

func (c *Controller) Render(extraRenderArgs ...interface{}) Result {
	...
}

5、Render函数中第一段代码是通过runtime.Caller获取函数调用的堆栈信息,主要是获取行号:

// Get the calling function name.
_, _, line, ok := runtime.Caller(1)

runtime.Caller()参数是1,也就是追溯上一级函数调用,所以获取的是Topic.New中 return c.Render(title)所在行号22。如果是runtime.Caller(0),获取的就是最近的函数调用信息了。

6、前面Revel已经把渲染的变量名字放到app/tmp/main.go中了,就可以通过line取回变量名了:

if renderArgNames, ok := c.MethodType.RenderArgNames[line]; ok {
	...
}

然后遍历变量并保存到c.RenderArgs中,这样就可以在模板中使用它了。

for i, extraRenderArg := range extraRenderArgs {
	c.RenderArgs[renderArgNames[i]] = extraRenderArg
}

可以看到最终还是用到了c.RenderArgs,对于单个模板变量,可以直接在Action中使用它赋值,带到模板中。

c.RenderArgs["user"] = user

如果变量比较多,用起来麻烦,也不美观,可以为控制器添加一个通用函数,比如:

type Vars map[string]interface{}

func (c *Application) bindVars(vars Vars) {
	for k, v := range vars {
		c.RenderArgs[k] = v
	}
}

这样使用起来就比较方便了

func (c Topic) Edit(id int64) revel.Result {
	title := "编辑帖子"

	var topic models.Topic
	has, _ := engine.Id(id).Get(&topic)
	if !has {
		return c.NotFound("帖子不存在")
	}

	c.bindVars(Vars{
		"title": title,
		"topic": topic,
	})

	return c.RenderTemplate("topic/New.html")
}

 

共3个回复
favor 回复

 总结的很好,之前不知道能够把多个变量放到 RenderArgs 里面,看了这篇文章才知道,revel 用起来很舒服。 希望社区壮大。

favor 回复

希望管理员能总结一篇 template 的使用技巧