4.5. View

4.5.1. IModelAndView - 模型和视图类结合
4.5.2. IModelAndTemplate - 模型和mint模板引擎结合
4.5.3. IModelAndSmarty - 模型和smarty模板引擎结合
4.5.4. IModelAndPHP - 模型以PHP变量输出
4.5.5. IModelAndXml - 模型以XML格式输出
4.5.6. IJSON - 模型以JSON格式输出
4.5.7. IJSONResponse - 模型以JSON标准响应格式输出

View就是显示层,一方面控制器通过传递数据模型给View显示不同格式、不同形式的界面,另一方面View还负责将用户的更新事件传递给控制器(点击比如界面上的一个按钮)。我们的View可以是一个类,也可以直接为一个模板(支持mint和Smarty),也可以为JSON或XML。

我们可以使用视图方案更方便地将数据模型传递给视图,不同的视图方案代表不同的输出格式。

4.5.1. IModelAndView - 模型和视图类结合

视图类是实现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

这是怎么工作的呢?

  1. 通过传入URL /helloWorld/index 调用HelloWorldController的appIndex方法

  2. appIndex方法返回一个视图方案IModeAndView对象

  3. 控制器调用IModelAndView对象的invoke方法

  4. IModelAndView对象的invoke方法检查视图类是否存在,比如不存在抛出异常,如果存在则调用和控制器对应的方法(appIndex)并传入模型$model

  5. HelloWorldView的appIndex方法接受模型$model,计算模型中a和b元素的和并输出


4.5.1.1. 为什么需要视图类?

从上面的例子可以看出,视图类并不能做任何具体显示的事情,而我们下面介绍的模板和XML、JSON都可以以特定的格式输出。那么为什么要使用视图类呢?答案是

  • 视图类与具体显示无关,保证了控制器、模型和视图的分离,使得具体的显示放在了View层,而控制器则毫不关心,在视图类里可以根据数据模型决定使用那种视图。

  • 既然控制器、模型和视图可以分离,那么就很适合多人分工开发,每一层只需知道其他层的接口即可独立完成自己模块的开发,并可单独测试

  • 另外,视图类可以根据特定视图需要再次组装模型中的数据,而显然控制器不应该关心这些

4.5.2. IModelAndTemplate - 模型和mint模板引擎结合

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是我们推荐使用的模板引擎,容易使用而且运行速度快,在可预见的未来内,它的组件将会更加丰富而优雅。

4.5.3. IModelAndSmarty - 模型和smarty模板引擎结合

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起到了桥的角色,不知不觉中连接了数据模型和模板。

4.5.4. IModelAndPHP - 模型以PHP变量输出

模型可以分解成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

4.5.5. IModelAndXml - 模型以XML格式输出

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>

4.5.6. IJSON - 模型以JSON格式输出

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}}

4.5.7. IJSONResponse - 模型以JSON标准响应格式输出

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"$modelnull);
    }
}

访问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列表告知客户端开发人员,以便他们根据这个值做出不同的处理。

4.5.7.1. AJAX示例:利用J-Framework和服务器间通讯

客户端放置在 /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按钮,就会提示服务器端返回的信息。