《Yii 1.1应用程序开发实例》

第三章:Ajax 与 JQuery

本章中,我们将涵括以下内容:

  • 通过Ajax加载局部页面
  • 内部资源管理
  • 在页面中引入资源
  • 使用JSON
  • 将参数从PHP传递到JS中
  • 处理表单中字段数量可变的情况

介绍

Yii的客户端是构建于jQuery构建的,jQuery是目前使用最广泛的JS类库,它功能强大且易于学习。在这个章节,我们将主要学习jQuery在Yii框架中的应用。如果你需要学习jQuery,请参照jQuery官网文档http://docs.jquery.com

通过Ajax加载局部页面

当下,异步刷新页面已经流行起来。现在,让我们实现一个”每日一语“的简单提醒功能。功能主要 是随机展现一句名言, 并且会有一个”下一个”的链接按钮来切换。

准备工作

  • 参考官方文档,使用yiic命令创建一个新的应用
  • 为该应用配置一个简洁的URL。

怎么做...

进行以下步骤:

  1. 创建一个控制器protected/controllers/QuoteController.php:
<?php
class QuoteController extends Controller
{
    private $quotes = array(
        array('你要相信世界上每一个人都精明,要令人信服并喜欢和你交往,那才最重要。', '李嘉诚'),
        array('该放下时且放下,你宽容别人,其实是给自己留下来一片海阔天空。', '于丹'),
        array('免费,是世界上最贵的东西。', '马云'),
        array('没有捕捉不到的猎物,就看你有没有野心去捕;没有完成不了的事情,就看你有没有野心去做。', '狼图腾'),
        array('君志所向,一往无前,愈挫愈勇,再接再厉。', '孙中山'),
    );
    
    private function getRandomQuote()
    {
        return $this->quotes[array_rand($this->quotes, 1)];
    }
    
    function actionIndex()
    {
        $this->render('index', array(
            'quote' => $this->getRandomQuote()
        ));
    }
    
    function actionGetQuote()
    {
        $this->renderPartial('_quote', array(
            'quote' => $this->getRandomQuote(),
        ));
    }
}
  1. 我们需要两个视图。第一个是protected/views/quote/index.php:
<h2>每日一句</h2>
<div id="quote-of-the-day">
    <?php $this->renderPartial('_quote', array(
        'quote' => $quote,
    ))?> 
</div>
<?php echo CHtml::ajaxLink('下一个', array('getQuote'),
        array('update' => '#quote-of-the-day'))?>

第二个视图是protected/views/quote/_quote.php。

&ldquo;<?php echo $quote[0]?>&rdquo;, <?php echo $quote[1]?>
  1. 现在,访问你的quote控制器,并点击”下一个“链接,怎么样,简单吧。

它是如何工作的...

首先,我们在 quote 的控制器里定义了一个私有属性: $quote,和一个方法来获取随机名言 (注: 在一个真实的项目中,你应该使用DAO或AR从数据库获取值)。

然后,我们为 index 操作定义了一个视图(index)和一个局部视图(_quote)。 这个局部视图被getQuote操作渲染时,是没有布局的,并且是 index 的视图页面的组成部分。 在index操作中,我们使用CHtml:ajaxLink来创建链接。它创建了一个访问getQuote操作的请求,并且在响应中,根据页面中的”quote-of-the-day”ID更新了HTML的元素。CHtml:ajaxLink在结果页面中生成了以下代码:

<script type="text/javascript">
/*<![CDATA[*/
jQuery(function($) {
   jQuery('body').delegate('#yt0','click',function(){
        jQuery.ajax({
            'url':'/quote/getQuote',
            'cache':false,
            'success':function(html){
                jQuery("#quote-of-the-day").html(html)
            }
        });
        return false;
    });
});
/*]]>*/
</script>

自从jQuery被Yii框架深度集成后,它就会自动的在页面中加载,使用起来非常方便。 并且不 管我们在页面中使用了多少次,它仅会被加载一次。

在index视图中,可以看到Yii为我们生成了一个叫做#yt0的ID。这样,就不用担心手动设定ID带来的烦恼。 不过,如果在页面中使用了ajax加载局部,而这个被包含进来的页面使用了带有JS功能的插件,或者使用了CHtml AJAX helpers,那么为了避免ID冲突,就需要手动设定这些ID了。

还有更多...

如果你想要自定义ajax的请求成功后的回调函数,那么可以通过第三个参数来实现:

<?php echo CHtml::ajaxLink('Next quote', array('getQuote'),
array('success' => 'js:function(data){
    alert(data);
}'))?>

注: 这里我们使用了”js:”前缀,当你使用了JS代码,而非字符串的时候,需要使用这个作为标识。

在一些情况下,我们需要getQuote操作只能通过Ajax 访问,那么有两种方法来限定它。第一种方法,使用内置的ajaxOnly过滤器:

class QuoteController extends Controller
{
    function filters()
    {
        return array(
            'ajaxOnly + getQuote',
        );
    }
...

添加完过滤器之后,再直接访问getQuote操作,就会得到一个400错误。

第二种方法,使用一个特殊的方法去探测这个请求是不是Ajax的。例如,如果我们需要将非Ajax的请求重定向到一个标准的404错误页面,可以这样做:

function actionGetQuote()
{
    if(!Yii::app()->request->isAjaxRequest)
        throw new CHttpException(404);
        
    $this->renderPartial('_quote', array(
        'quote' => $this->getRandomQuote(),
    ));
}

还有一些方法可以让Action同时接受Ajax和非Ajax 请求:

function actionGetQuote()
{
    $quote = $this->getRandomQuote();
    if(Yii::app()->request->isAjaxRequest)
    {
        $this->renderPartial('_quote', array(
            'quote' => $quote,
        ));
    }
    else {
        $this->render('index', array(
            'quote' => $quote,
        ));
    }
}
Prevent including a bundled jQuery

Sometimes, you need to suppress including a bundled jQuery. For example, if your project code relies on version specific functionality. To achieve this, you need to configure a clientScript application component using protected/config/main.php as follows:

return array(
    // ...
    // application components
    'components'=>array(
        // ...
        'clientScript' => array(
            'scriptMap' => array(
                'jquery.js'=>false,
                'jquery.min.js'=>false,
            ),
        ),
    ),
    
    // ...
);
进一步阅读

如需进一步况读,请参阅以下网址:

另请参阅

内部资源(assets)管理

有效管理内部资源是Yii众多的优秀特性之一。在以下的情况中,这种特性非常有用:

  • 当你在实现一个扩展插件的时候,它所包含的JS,图片和CSS都保存在自己的文件夹里。此时,你是无法通过浏览器直接访问他们。
  • 当你需要预加载你的内部资源时,即绑定并编译JS等等。
  • 当你在在多个页面使用内部资源,但又不想重复写同样的的代码。

前两条其实可以算是附加的特性,而第三条则解决了小部件重用的问题。

下面,让我们创建一个简单的处理Facebook事件的小部件。它将使用自己的CSS,JS和图片文件。

准备工作

  • 参考官方文档,使用yiic命令创建一个新的应用。
  • 检查应用的根目录(index。php所在的目录)下assets文件夹是否有写入权限。
  • 从网站http://ajaxload.info/上下载一个显示加载中的的动态gif图片。

怎么做...

首先,让我们计划一下。在Yii中,你可以在任何虚拟路径放置你的小部件。大多数情况下,我们都放在protected/compoments目录下,当然,如果只有一两个类文件的话也无所谓。但是,当文件越来越多的时候,就会有点乱了。所以,让我们放到 protected/extensions/facebook_events目录下吧。 首先在这个目录下,创建assets文件夹,然后把之前下载的gif图片放进来,并重命名为loading.gif。 最后在这个目录下,创建facebook_events.css和facebook_events.js两个文件。

  1. 接下来,让我们从挂件的类文件开始。protected/extensions/facebook_events/EFacebookEvents.php:
<?php
class EFacebookEvents extends CWidget
{
    public $keyword;
    private $loadingImageUrl;
    protected $url = "https://graph.facebook.com/search?q=%s&type=event&callback=?";
            
    protected function getUrl()
    {
        return sprintf($this->url, urlencode($this->keyword));
    }
    
    public function init()
    {
        $assetsDir = dirname(__FILE__).'/assets';
        $cs = Yii::app()->getClientScript();
        
        $cs->registerCoreScript("jquery");
        
        // 发布并注册js文件
        $cs->registerScriptFile(
            Yii::app()->assetManager->publish(
                $assetsDir.'/facebook_events.js'
            ),
            CClientScript::POS_END
        );
        
        // 发布并注册css文件
        $cs->registerCssFile(
            Yii::app()->assetManager->publish(
                $assetsDir.'/facebook_events.css'
            )
        );
        
        // 发布图片。返回可访问资源实际的URL
        $this->loadingImageUrl = Yii::app()->assetManager->publish(
            $assetsDir.'/ajax-loader.gif'
        );
    }
 
    public function run()
    {
        $this->render("body", array(
            'url' => $this->getUrl(),
            'loadingImageUrl' => $this->loadingImageUrl,,
            'keyword' => $this->keyword,
        ));
    }
}
  1. 我们来定义在”run”方法中使用到的”body”视图protected/extensions/ facebook_events/views/body.php:
<div class="facebook-events" data-url="<?php echo $url?>">
    <h2><?php echo $keyword?> events</h2>
    <div class="data">
        <?php echo CHtml::image($loadingImageUrl)?>
    </div>
</div>
  1. 然后,需要一个facebook_events.js:
jQuery(function($){
    $(".facebook-events").each(function(){
        var url = $(this).data("url");
        var container = $(".data", this);
        
        $.getJSON(url,function(json){
            var html = "<ul>";
            $.each(json.data,function(){
                html += "<li>"+
                    "<p><strong>" + this.name + "</strong>
                        </p><p>"+this.location
                    "</p></li>";
            });
            html += "</ul>";
            container.html(html);
        });
    });
});
  1. 创建facebook_events.css:
.facebook-events {
    padding: 10px;
    width: 400px;
    float: left;
}
 
.facebook-events ul {
    padding: 0;
}
 
.facebook-events li {
    list-style: none;
    border: 1px solid #ccc;
    padding: 10px;
    margin: 2px;
}
  1. 完成! 我们的挂件已经准备好了。让我开始使用它。打开protected/site/index.php,然后,添加以下代码:
<?php $this->widget("ext.facebook_events.EFacebookEvents", array(
    'keyword' => 'php',
))?>
 
<?php $this->widget("ext.facebook_events.EFacebookEvents", array(
    'keyword' => 'jquery',
))?>
  1. 现在,打开你应用程序的主页,应该会有两个地方使用了Facebook events的PHP和jQuery代码,类似下面的截图。

它是如何工作的...

当我们在使用site/index视图中的$this->widget时,两个FacebookEvents的方法被调用:init方法发布assets内部资源,并在视图页面中添加链接地址,render方法渲染挂件的HTML文件。首先,我们使用CAssetManager::publish 来复制我们的文件到可以被直接访问的assets路径下。它返回可以被直接被访问的资源的URL。然后,使用 CClientScript 的方法通过添加<script>和<style>标签的方式引入JS和CSS,该方法能自动避免重复引入。最后使用CHtml::image将 图片的URL地址传递到”body”的视图中去渲染占位符。当JS被加载后,它将向Facebook的API(参考 http://developers.facebook.com/docs/api)发送请求,然后使用收到的数据替换相应 的占位符。

还有更多...

There is more about working with assets.

assets 文件夹里有什么呢?

让我们检查下assets文件夹。内容应该像下面这个样子:

assets
    1a6630a0\
        main.css
    2bb97318\
        pager.css
    4ab2ffe\
        jquery.js
        ...

采用1a6630a0这种命名方式主要是为了将同名的文件区分开,避免命名冲突。这种命名是使用哈希计算公开的asset全路径得来,所以assets 其实是从同样的位置复制到同样的路径下。而这也意味着,如果同时公开图片和CSS文件,那么你可以在CSS文件中使用相对路径来使用图片。

发布整个目录

使用CAssetManager::publish, 你能返回发布整个目录。但不同是,单个文件的在发布之后, 每一 个文件是被镜像复制的, 而目录不是。

进一步阅读

如需进一步况读,请参阅以下网址:

另请参阅

  • 第八章: 扩展Yii中的创件挂件小节

在页面中引入资源

Yii有一个特殊的类,叫做CClientScript,它可以帮助我们在页面中引入脚本,CSS和其他资源。

怎么做...

让我们首先引入一个脚本,脚本有三种形式: 外部脚本,核心脚本和内联脚本.

  1. 外部脚本是使用一个可通过URL访问的脚本文件,例如,引入http://example.com/js/main.js文件,你可以使用如下代码:
Yii::app()->clientScript->registerScriptFile("http://example.com/js/main.js");
  1. 为了控制脚本被引入页面存在的位置,我们可以使用以下约束条件作为第二个参数:
CClientScript::POS_HEAD在title标签之前
CClientScript::POS_BEGIN在body标签开始的位置
CClientScript::POS_END在body标签结束的位置
  1. 核心脚本是和Yii绑定在一起的脚本。例如jQuery,你可以使用以下方式引入一个核心脚本:
Yii::app()->clientScript->registerCoreScript('jquery');
  1. 所有包被罗列在framework/web/js/packages.php中:
return array(
    'jquery'=>array(
         'js'=>array(YII_DEBUG ? 'jquery.js' : 'jquery.min.js'),
    ),
    'yii'=>array(
        'js'=>array('jquery.yii.js'),
        'depends'=>array('jquery'),
    ),
    'yiitab'=>array(
        'js'=>array('jquery.yiitab.js'),
        'depends'=>array('jquery'),
    ),
    'yiiactiveform'=>array(
        'js'=>array('jquery.yiiactiveform.js'),
        'depends'=>array('jquery'),
    ),
    'jquery.ui'=>array(
        'js'=>array('jui/js/jquery-ui.min.js'),
        'depends'=>array('jquery'),
    ),
...
  1. 在这个列表中,jquery,yii,yiiactiveform都是在registerClientScript参数中可以使用的。而对应 的array内的参数是真实的脚本文件,它们的相对路径为: framework/web/js/source。
  1. 内联脚本是直接写在页面中的脚本。很典型的是,它们在每一个请求中都是不同的。可以通过以下的方式来使用这种类型的脚本:
Yii::app()->clientScript->registerScript('myscript', 'echo "Hello, world!";', CClientScript::POS_READY);

第一个参数是,你已经选择的脚本ID,不可重复.。第二个参数是,,脚本的内容.。而第三个参数是告诉Yii,将这段脚本加入到页面的什么位置。另外还有两个位置可以在registerScriptFile()方法中使用:  

CClientScript::POS_LOAD在window.onload()方法中调用。
CClientScript::POS_READY在jQuery的准备方法中调用。

现在让我们来学习引入CSS的方法。CSS存在两种类型: 内联和外部,(没有核心类型)。

  1. 外部引入的方式
Yii::app()->clientScript->registerCssFile ('http://example.com/css/main.css');

另外,这个方法含有第二个参数可选,允许用户自定义CSS的media类型。如screen或者print等等。但没有第三个参数来自定义CSS的引入位置,毕竟CSS文件唯一有效的位,就是head标签内。

内联CSS应该尽可能避免,但是如果你真的需要,可以这么做: 

Yii::app()->registerCss('myCSS', 'body {margin: 0; padding: 0}', 'all');
  1. 在这行代码中,myCSS为唯一的ID,接下来,是真实的CSS代码,以及media类型。

它是如何工作的...

Yii 中的CClientScript方法其实并不是调用就立即执行的。被替代的资源是直到控制器调用render()方法的时候才会被存储。然后在布局中的适当位置,插入所有需要的脚本以及CSS的块和标记,如果我们引入的资源中有两个同样的名字或 URL,那么它仅会被引入一次。因此,我们可以很自由的在挂件,属部视图或者任何可重用代码块中调用资源,而不用担心最后生成的视图页面会有代码冗余。

还有更多...

现在我们已经学习了大部分的资源类型: JavaScript 和 CSS,但是还有其他的类型。

使用自定义脚本包

While using Yii, you can leverage package features to manage script dependencies in the same way core dependencies are managed. The feature is described well at the following API page:http://www.yiiframework.com/doc/api/CClientScript#packages-detail

注册链接的资源

CClientScript提供了另外一个方法,让我们注册一个自定义的<link>标签。例如,向特定的控制器的Action中,添加RSS链接:

Yii::app()->clientScript->registerLinkTag(
    'alternate',
    'application/rss+xml',
    $this->createUrl('rss/articles')
);
注册meta标签

另外,CClientScript还能够使用registerMetaTag注册meta标签,包括 description或者keywords。例如,指定文档编码:

Yii::app()->clientScript->registerMetaTag(' text/html;charset=utf-8', null, 'Content-Type');
进一步阅读

如需进一步况读,请参阅以下网址:

另请参阅

使用JSON

JSON是一个简单,封装完善,并且广泛应用于AJAX数据传递的格式。而Yii拥有一些非常便利的使用方法。首先让我创建一个新闻列表,然后每两秒钟更新一次。

准备工作

  1. 使用yiic工具创建一个新的Web应用程序。
  1. 创建并设置新数据库。
  1. 创建一个表名为news,并添加id,created_on,和title字段:
CREATE TABLE `news` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    `created_on` int(11) unsigned NOT NULL,
    `title` varchar(255) NOT NULL,
    PRIMARY KEY (`id`)
)
  1. 使用Gii生成News模型。

怎么做...

  1. 创建控制器protected/controllers/NewsController.php:
<?php
class NewsController extends Controller
{
    public function filters()
    {
        return array(
            'ajaxOnly + data',
        );
    }
    
    public function actionIndex()
    {
        $this->render('index');
    }
    
    public function actionData()
    {
        $criteria = new CDbCriteria();
        $criteria->order = 'created_on DESC';
        $criteria->limit = 10;
        $news = News::model()->findAll($criteria);
        echo CJSON::encode($news);
    }
    
    public function actionAddRandomNews()
    {
        $news = new News();
        $news->title = "Item #".rand(1, 10000);
        $news->created_on = time();
        $news->save();
        echo "OK";
    }
}
  1. 还要创建一个视图文件protected/views/news/index.php:
<div class="news-list">
    Loading...
</div>
 
<?php Yii::app()->clientScript->registerCoreScript("jquery")?>
<script type="text/javascript">
jQuery(function($) {
    var newsList = $('.news-list');
    function updateNews(){
        newsList.html("Loading...");
        $.ajax({
            url: "<?php echo $this->createUrl('data')?>",
            dataType: 'json',
            cache: false,
            success: function(data) {
                var out = "<ol>";
                $(data).each(function(){
                    out+="<li>"+this.title+"</li>";
                });
                out += "</ol>";
                newsList.html(out);
            }
        });
    }
    updateNews();
    setInterval(function(){
        updateNews()
    }, 2000);
});
</script>
  1. 现在,运行控制器中的index方法并尝试使用addRandomNews方法在数据库中添加一些记录。不刷新index页面。News将每两秒一次出现在index页面,类似下面的截图。

它是如何工作的...

index操作方法其实什么也没做。它仅仅是渲染了一个包含DIV和一些JS代码的视图文件。当然,我们在视图中使用了jQuery,肯定要先把它引入到页面中:

<?php Yii::app()->clientScript->registerCoreScript("jquery")?>

然后,我们在JS脚本中定义了函数updateNews(),并且每隔2000ms运行一次。在 updateNews()这个方法中,我们向NewsController下的data操作发送了AJAX请求,然后将相应中JSON格式的数据进行处理,并替换当前页面的DIV占位符。

在data操作中,我们得到了最新的news数据,并将他们使用CJSON::encode方法将查询结果转换为JSON格式的数据。

还有更多...

如需进一步况读,请参阅以下网址:

另请参阅

将参数从PHP传递到JavaScript中

相信大家都知道,在应用程序的配置文件protected/config/main.php中存储一些配置参数,然后,在其他地方使用 Yii::app()->params['paramName']就可以简单的进行访问。而如果你的应用程序中使用了JavaScript脚本,怎么让JavaScript也能轻松访问到这些参数呢?下面让我们一起学习下。

准备工作

  1. 使用yiic工具创建新的应用程序,它会在配置文件protected/config/main.php中自动生成应用程序的参数数组:
'params'=>array(
    // this is used in contact page
    'adminEmail'=>'webmaster@example.com',
),
  1. 添加附加的参数:
'params'=>array(
    // this is used in contact page
    'adminEmail'=>'webmaster@example.com',
    'alert' => array(
        'enabled' => true,
        'message' => 'Hello there!',
    ),
),

怎么做...

  1. 创建控制器protected/controllers/AlertController.php:
<?php
class AlertController extends Controller
{
    function actionIndex()
    {
        $config = CJavaScript::encode(Yii::app()->params->toArray());
        Yii::app()->clientScript->registerScript('appConfig', 
            "var config = ".$config.";",CClientScript::POS_HEAD);
        $this->render('index');
    }
}
  1. 还要创建视图文件protected/views/alert/index.php:
<script>
if(config && config.alert && config.alert.enabled &&
    config.alert.message){
    alert(config.alert.message);
}
</script>
  1. 现在,如果你运行alert控制器中index控制方法,你应该得到一个标准的JavaScript提醒窗口,内容为”Hello there!“,类似下面的截图:

它是如何工作的...

我们使用CJavaScript::encode()将数据从PHP结构转换为JavaScript结构。也就是把Yii应用中的参数列表转换成了JavaScript的数组,然后,我们指定了一个全局变量config。最后在视图中,JavaScript使用了这个全局变量config。

另请参阅

处理表单中字段数量可变的情况

有时候,在一个表单中需要输入框的数量是可变的。比如,任务管理类应用,提供了可以添加一个或多个任务到你的任务列表中的界面,类似下面的截图:

默认页面显示一个任务和两个按钮:添加任务将要在下方生成另一个空的任务表单,“保存”将要重载一个已添加任务的表单。下面让我们看看是怎么实现的。

准备工作

使用yiic工具创建新的应用程序。

怎么做...

为了使这个例子更加简洁,我们将不适用数据库进行数据保存。而重点内容在于如何获取一个字段数量可变的的表单,并且收集表单提交的数据。

  1. 让我们从任务模型protected/models/Task.php开始,由于不使用数据库,那么CFormModel就可以了:
<?php
class Task extends CFormModel
{
    public $title;
    public $text;
    public function rules()
    {
        return array(
            array('title', 'required'),
            array('text', 'safe'),
        );
    }
}
  1. 创建控制器protected/controllers/TaskController.php:
<?php
class TaskController extends Controller
{
    public function filters()
    {
        return array(
            'ajaxOnly + field'
        );
    }
    
    public function actionIndex()
    {
        $models = array();
        if(!empty($_POST['Task']))
        {
            foreach($_POST['Task'] as $taskData)
            {
                $model = new Task();
                $model->setAttributes($taskData);
                if($model->validate())
                    $models[] = $model;
            }
        }
        
        if(!empty($models)){
            // We've received some models and validated them.
            // If you want to save the data you can do it here.
        } else
            $models[] = new Task();
        $this->render('index', array(
                'models' => $models,
        ));
    }
 
    public function actionField($index)
    {
        $model = new Task();
        $this->renderPartial('_task', array(
            'model' => $model,
            'index' => $index,
        ));
    }
}
  1. 创建视图pretected/views/task/index.php:
<div class="form">
    <?php echo CHtml::beginForm()?>
        <ul class="tasks">
            <?php for($i=0; $i<count($models); $i++):?>
                <?php $this->renderPartial('_task', array(
                    'model' => $models[$i],
                    'index' => $i,
                ))?>
            <?php endfor ?>
        </ul>
        
        <div class="row buttons">
            <?php echo CHtml::button('Add task',
                array('class' => 'tasks-add'))?>
      
            <?php Yii::app()->clientScript->registerCoreScript("jquery")?>
            <script>
                $(".tasks-add").click(function(){
                $.ajax({
                    success: function(html){
                        $(".tasks").append(html);
                    },
                    type: 'get',
                    url: '<?php echo $this->createUrl('field')?>',
                    data: {
                        index: $(".tasks li").size()
                    },
                    cache: false,
                        dataType: 'html'
                    });
                });
            </script>
            <?php echo CHtml::submitButton('Save')?>
        </div>
    <?php echo CHtml::endForm()?>
</div>
  1. 最后,创建一个局部视图protected/views/task/_task.php:
<li>
    <div class="row">
        <?php echo CHtml::activeLabel($model, "[$index]title")?>
        <?php echo CHtml::activeTextField($model, "[$index]title")?>
    </div>
    <div class="row">
        <?php echo CHtml::activeLabel($model, "[$index]text")?>
        <?php echo CHtml::activeTextArea($model, "[$index]text")?>
    </div>
</li>
  1. 完成。现在让我们运行task控制器的index操作方法并尝试添加任务,类似下面的截图:

它是如何工作的...

让我们回顾控制器的index操作方法。虽然,我们需要收集不仅仅是一条数据,但其实也没有很大的区别。对于单一的模型来说,使用$_POST[‘model_name’]传递提交表单的数据。唯一不同的是,在我们的例子中,会有如下的数据项的数组:

if(!empty($_POST['Task']))
{
    foreach($_POST['Task'] as $taskData)
    {
        $model = new Task();
        $model->setAttributes($taskData);
        if($model->validate())
            $models[] = $model;
    }
}

对每条数据,我们都创建一个Task模型,然后使用这条数据的内容去设定模型的属性值。最后如果模型有效,则将模型放入$models数组中。

if(!empty($models)){
    // We've received some models and validated them.
    // If you want to save the data you can do it here.
}
else
    $models[] = new Task();

如果$models数组不是空,则有从表单传递过来的数据并且是有效的。如果$models没有数据,则我们还需要一个模型用于显示表单。

实际上,在这个例子中,我们并没有保存数据。当我们使用Active Record模型,我们可以使用$model->save()保存数据。然后,我们通过索引渲染表单视图:

<?php for($i=0; $i<count($models); $i++):?>
    <?php $this->renderPartial('_task', array(
        'model' => $models[$i],
        'index' => $i,
    ))?>
<?php endfor ?>

由于有多个模型,我们需要渲染一个字段。这是在_task局部视图完成的:

<li>
    <div class="row">
        <?php echo CHtml::activeLabel($model, "[$index]title")?>
        <?php echo CHtml::activeTextField($model, "[$index]title")?>
    </div>
    <div class="row">
        <?php echo CHtml::activeLabel($model, "[$index]text")?>
        <?php echo CHtml::activeTextArea($model, "[$index]text")?>
    </div>
</li>

请注意我们如何使用动态标签和动态字段。对于第一个字段,我们指定了一个模型和格式为[model_index]field_name的名字。前面的代码会生成下面的HTML:

<li>
    <div class="row">
        <label for="Task_0_title">Title</label>
            <input name="Task[0][title]" id="Task_0_title" type="text" value="" />
    </div>
    <div class="row">
        <label for="Task_0_text">Text</label>
        <textarea name="Task[0][text]" id="Task_0_text"></textarea>
    </div>
</li>
 
<li>
    <div class="row">
        <label for="Task_1_title">Title</label>
        <input name="Task[1][title]" id="Task_1_title" type="text" value="" />
    </div>
    <div class="row">
        <label for="Task_1_text">Text</label>
        <textarea name="Task[1][text]" id="Task_1_text"></textarea>
    </div>
</li>

字段名类似Task[0][title]是PHP特殊功能。当提交后,这样名字的参数会自动解析为PHP数组。

现在,让我们回顾一下添加任务是怎么工作的:

<?php echo CHtml::button('Add task', array('class' => 'tasks-add'))?>
<?php Yii::app()->clientScript->registerCoreScript("jquery")?>
<script>
    $(".tasks-add").click(function(){
        $.ajax({
            success: function(html){
                $(".tasks").append(html);
            },
            type: 'get',
            url: '<?php echo $this->createUrl('field')?>',
            data: {
                index: $(".tasks li").size()
            },
            cache: false,
            dataType: 'html'
        });
    });
</script>

我们添加了一个按钮,它class属性为tasks-add,然后使用jQuery脚本为其指定onClick处理事件。当这个按钮被点击之后, 我们会发送AJAX请求到控制器中的filed操作方法,参数为$index, 而index的值等于表单中任务内容的数量。然后,field操作响应一个局部的HTML代码,最后把这些代码追加到表单列表中。

还有更多...

You can find additional information about handling multiple inputs in the official guide at the following URL:http://www.yiiframework.com/doc/guide/en/form.table

另请参阅

评论 X

      友荐云推荐
      Copyright 2011-2014. YiiBook.com