View就是显示层,一方面控制器通过传递数据模型给View显示不同格式、不同形式的界面,另一方面View还负责将用户的更新事件传递给控制器(点击比如界面上的一个按钮)。我们的View可以是一个类,也可以直接为一个模板(支持mint和Smarty),也可以为JSON或XML。
我们可以使用视图方案更方便地将数据模型传递给视图,不同的视图方案代表不同的输出格式。
视图类是实现IView接口的类(当然IView目前未定义任何方法),视图方案会调用其跟控制器一致的方法,并传入模型,比如调用了控制器中appIndex方法,那么视图类中必须有对应的方法,并可以接受一个参数作为模型,当index动作执行时将调用该方法。
例 4.2. 使用视图类
class HelloWorldController extends IApplication {
public function appIndex() {
$model = array("a"=>1, "b"=>2);
return new IModelAndView($model, "HelloWorldView");
}
}
class HelloWorldView implements IView {
public function appIndex($model) {
echo $model["a"] + $model["b"];
}
}
在刚才的HelloWorldController类中我们添加了一个HelloWorldView视图类,它实现了IView接口,然后修改appIndex方法,使其返回了一个IModelAndView对象,第一个参数为模型(我们说过:任何类型的数据都可以作为模型),第二个参数即为HelloWorldView类的名称,其他一切不变,运行之后的结果为:
3
这是怎么工作的呢?
通过传入URL /helloWorld/index 调用HelloWorldController的appIndex方法
appIndex方法返回一个视图方案IModeAndView对象
控制器调用IModelAndView对象的invoke方法
IModelAndView对象的invoke方法检查视图类是否存在,比如不存在抛出异常,如果存在则调用和控制器对应的方法(appIndex)并传入模型$model
HelloWorldView的appIndex方法接受模型$model,计算模型中a和b元素的和并输出
mint是我们开发的一个“编译型”模板引擎,是I-F中的核心模块之一,而且是控制器默认使用的模板引擎,它的最大特点是使用XML或者XHTML节点属性定义组件,更深入的细节我们在“mint模板引擎”一章中讲述,这里仅列出一个简单的例子,还是修改上面的HelloWorld控制器。
然后修改HelloWorld控制器为:
class HelloWorldController extends IApplication {
public function appIndex() {
$this->model = array("a"=>1, "b"=>2);
}
}
在这个程序里,我们只是给当前控制器添加一个属性model,值为 array("a"=>1, "b"=>2),然后就可以在模板里直接使用它。
现在定义 mint/template/hello_world/index.html
<html>
<head>
<title>hello,world</title>
</head>
<body>
hello,world! model is
<ul com="@foreach" x-data-set="$model" x-key="$k" x-value="$v">
<li>key is {$k} and value is {$v}</li>
</ul>
</body>
</html>
我们在index.html中使用了mint组件@foreach,可以看到它的所有参数(x-data-set,x-key,x-value)均定义在<ul>节点上,当然还可以使用类似于JSTL的方式进行书写。
调用/test/index.php/helloWorld/index之后的浏览器上输出为:
hello,world! model is
key is a and value is 1
key is b and value is 2
查看网页源文件得到
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<head><title>hello,world</title></head>
<body>
hello,world! model is
<ul><li>key is a and value is 1</li>
<li>key is b and value is 2</li>
</ul>
</body>
</html>
说明我们已经成功遍历了$model中的元素,并以HTML的形式显示出来。
mint是我们推荐使用的模板引擎,容易使用而且运行速度快,在可预见的未来内,它的组件将会更加丰富而优雅。
Smarty是世界著名的PHP模板引擎,它具有需求版本低、速度快、语法丰富、插件丰富以及方便自定义等优点,应用已经非常普及,我们也提供了IModelAndSmarty类方便程序员在I-F里使用Smarty。
使用IModelAndSmarty你无需了解Smarty具体的工作方式就可以很方便的将变量赋给模板。在使用该视图方案之前需要先安装Smarty,具体请见Smarty官方文档。我们安装后的目录结构为:
/ipa
/app
/smarty
/templates
/templates_c
/lib/Smarty-2.6.19
Smarty.class.php
/index.php
可以发现,相对于IModelAndTemplate的例子,我们只是把smarty程序包拷贝到lib/目录下,如果没有lib/,需要新建,而lib/在运行时,会自动加到include_path中。
还是修改HelloWorld控制器:
class HelloWorldController extends IApplication {
public function appIndex() {
require_once "Smarty-2.6.19/Smarty.class.php";
//设置smarty选项
IModelAndSmarty::setSmartyOptions(array(
"compile_check" => true,
"debugging" => false,
"left_delimiter" => "{",
"right_delimiter" => "}",
"template_dir" => IFRAMEWORK_APP_ROOT . "/app/smarty/templates",
"compile_dir" => IFRAMEWORK_APP_ROOT . "/app/smarty/templates_c"
));
//设置变量
$this->model = array("a"=>1, "b"=>2);
//返回
return new IModelAndSmarty($this, "hell_world/index.html");
}
}
现在修改 index.html
<html>
<head>
<title>hello,world</title>
</head>
<body>
hello,world! model is
<ul>
{foreach key=k item=v from=$model}
<li>key is {$k} and value is {$v}</li>
{/foreach}
</ul>
</body>
</html>
然后再访问index.php,发现浏览器上打印出了
hello,world! model is
key is a and value is 1
key is b and value is 2
查看源文件,HTML代码为:
<html>
<head>
<title>hello,world</title>
</head>
<body>
hello,world! model is
<ul>
<li>key is a and value is 1</li>
<li>key is b and value is 2</li>
</ul>
</body>
</html>
哇!你会惊奇的发现,我们的程序和代码都和mint的使用方法十分类似!事实上,mint组件并非学自Smarty,出现这样的巧合也会是因为大家的想法一致,也许都无意中学习了别的同一种模板引擎......
在这个例子中,我们没有调用$smarty->assign就输出了正确的内容,IModelAndSmarty起到了桥的角色,不知不觉中连接了数据模型和模板。
模型可以分解成PHP可以访问的变量。
修改HelloWorld控制器为:
class HelloWorldController extends IApplication {
public function appIndex() {
$this->text"] = "i am text";
$model["array"] = array("a"=>1, "b"=>2, "c"=>3);
$model["1"] = "i am invalid";
return new IModelAndPHP($model, IFRAMEWORK_APP_ROOT . "/app/controllers/my.php");
}
}
?>
然后在/app/controllers目录下添加 my.php :
test: <?=$text?><br/>
array: <?php
print_r($array);
?>
<br/>
1: <?php echo $var_1; ?>
访问/helloWorld/index,浏览器输出:
test: i am text
array: Array ( [a] => 1 [b] => 2 [c] => 3 )
1: i am invalid
需要特别注意的是,对于不能为变量名的"1",I-F在其前面加入了一个前缀var_,从而变成了$var_1。
IModelAndXml视图方案可以让数据以XML格式输出,无需程序员自己转换。
修改HelloWorld控制器为:
class HelloWorldController extends IApplication {
public function appIndex() {
$model["text"] = "我是中国人";
$model["array"] = array("a"=>1, "b"=>2, "c"=>3);
return new IModelAndXml($model, array("version"=>"1.0", "encoding"=>"gbk"));
}
}
访问/helloWorld/index,浏览器上输出一个XML:
<?xml version="1.0" encoding="gbk" ?>
<data>
<text>
<![CDATA[ 我是中国人 ]]>
</text>
<array>
<a>
<![CDATA[ 1 ]]>
</a>
<b>
<![CDATA[ 2 ]]>
</b>
<c>
<![CDATA[ 3 ]]>
</c>
</array>
</data>
IJSON视图方案格式化数据为JSON格式并输出,适合当前流行的AJAX开发需求。
修改HelloWorld控制器为
class HelloWorldController extends IApplication {
public function appIndex() {
$model["text"] = "我是中国人";
$model["array"] = array("a"=>1, "b"=>2, "c"=>3);
return new IJSON($model);
}
}
访问index.php,浏览器将会输出:
{"text":"\u6211\u662f\u4e2d\u56fd\u4eba","array":{"a":1,"b":2,"c":3}}
IJSONResponse同IJSON一样将数据格式化为JSON格式并输出,只不过,你可以往里面添加更多的信息,比如:
code - 响应代号
message - 响应信息
data - 响应数据
exception - 响应的异常信息
该对象描述了客户端需要的更完整的响应信息。
修改HelloWorld控制器为
class HelloWorldController extends IApplication {
public function appIndex() {
$model["text"] = "我是中国人";
$model["array"] = array("a"=>1, "b"=>2, "c"=>3);
return new IJSONResponse(200, "success", $model, null);
}
}
访问index.php,浏览器将会输出:
{"code":200,"message":"success","exception":null,"data":{"text":"\u6211\u662f\u4e2d\u56fd\u4eba","array":{"a":1,"b":2,"c":3}}}
通过返回不同的code和message,客户端就可以很方便地判断响应的结果;通过exception值,客户端就知道是否有异常发生。我们一般将正常处理的响应code设为2xx,比如200,201,202......,把错误处理的响应code设为5xx,比如500,501,502......,在具体的应用中,视不同的情况而定,服务器端开发人员应当把响应code列表告知客户端开发人员,以便他们根据这个值做出不同的处理。
客户端放置在 /public/ajax.html,内容为:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Ajax test</title>
<script language="javascript" src="/javascripts/JLib_0.4.1.js"></script>
<script language="javascript">
function submitForm(form) {
var http = new JHttp();
//注册事件监听器
http.register({
"completed": function (response) {
var json = JSON.decode(response.getText());
alert(json.message);
switch (json.code) {
case 401:
form.username.focus();
break;
case 402:
form.password.focus();
break;
}
}
});
//发送请求
http.post("/helloWorld/check?username=" + form.username.value + "&password=" + form.password.value);
}
</script>
</head>
<body>
<!-- 要提交的表单 -->
<form method="post" action="" onsubmit="submitForm(this);return false;">
username: <input type="text" name="username"/><br/>
password: <input type="password" name="password"/><br/>
<input type="submit" value="submit"/>
</form>
</body>
</html>
服务器端HelloWorldController加入一个新的动作check:
class HelloWorldController extends IApplication {
public function appCheck() {
//获取参数
$username = trim($this->in->username);
$password = trim($this->in->password);
//检查
if ($username == "") {
return new IJSONResponse(401, "username can't be empty");
}
if ($password == "") {
return new IJSONResponse(402, "password can't be empty");
}
//返回成功
return new IJSONResponse(200, "ok");
}
}
现在访问 /ajax.html,点击submit按钮,就会提示服务器端返回的信息。