《Yii1.1 Application Development Cookbook》

Chapter 3: Ajax and jQuery

In this chapter, we will cover:

  • Loading a block through AJAX
  • Managing assets
  • Including resources into the page
  • Working with JSON
  • Passing configuration from PHP to JavaScript
  • Handling variable number of inputs

Introduction

Yii's client side is built with jQuery—the most widely used JavaScript library which is very powerful and simple to learn and use. In this chapter, we will focus on Yii-specific tricks rather than jQuery itself. If you need to learn more about jQuery, then please refer to its documentation at http://docs.jquery.com/.

Loading a block through AJAX

Nowadays, it's common when a part of a page is loaded asynchronously. Let's implement the quotes box which will display random quotes and will have the "Next quote" link to show the next one.

Getting ready

  • Create a fresh Yii application using yiic webapp as described in the official guide
  • Configure application to use clean URLs

How to do it...

Carry out the following steps:

  1. Create a new controller named protected/controllers/QuoteController. php as follows:
<?php
class QuoteController extends Controller
{
    private $quotes = array(
        array('Walking on water and developing software from a
            specification are easy if both are frozen.', 'Edward V Berard'),
        array('It always takes longer than you expect, even when you
            take into account Hofstadter&rsquo;s Law.', 'Hofstadter&rsquo;s
            Law'),
        array('Always code as if the guy who ends up maintaining
            your code will be a violent psychopath who knows where you live.',
            'Rick Osborne'),
        array('I have always wished for my computer to be as easy to
            use as my telephone; my wish has come true because I can no longer
            figure out how to use my telephone.', 'Bjarne Stroustrup'),
        array('Java is to JavaScript what Car is to Carpet.', 'Chris Heilmann'),
    );
    
    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. We will require two views. The first is protected/views/quote/index.php:
<h2>Quote of the day</h2>
<div id="quote-of-the-day">
    <?php $this->renderPartial('_quote', array(
        'quote' => $quote,
    ))?> 
</div>
<?php echo CHtml::ajaxLink('Next quote', array('getQuote'),
        array('update' => '#quote-of-the-day'))?>

The second view named protected/views/quote/_quote.php is as follows:

&ldquo;<?php echo $quote[0]?>&rdquo;, <?php echo $quote[1]?>
  1. That is it. Now, try to access your quote controller and click on the Next quote link, as shown in the following screenshot:

How it works...

First, we define a list of quotes in the controller's private property $quotes and create a method to get a random quote. In a real application, you will probably get a quote from a database using DAO or an active record.

Then, we define a view for the index action and _quote which is used in the getQuote action that renders it without layout and the index view as a partial. In the index action, we use CHtml::ajaxLink to create a link which makes a request to the getQuote action and updates the HTML element with the ID of quote-of-the-day. This is done with a response CHtml::ajaxLink that generates the following code in the resulting HTML page (reformatted):

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

As jQuery is being used, Yii includes it in the page automatically and does it only once, no matter how many times we are using it.

You can see that Yii generated a #yt0 ID for us. That is great because you don't have to worry about setting IDs manually. Nevertheless, if you are loading a part of the page through AJAX and this part includes JavaScript-enabled widgets or CHtml AJAX helpers, then you need to set IDs manually because of possible IDs intersection.

There's more...

If you want to customize the success callback, then you can do this by setting it through a third parameter as follows:

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

Note that we used the js: prefix, which is required when you want to use JavaScript instead of a string, as in this example.

In some cases, it is not desirable to allow a non-AJAX access to the getQuote action. There are two ways through which we can limit its usage to AJAX-only. First, you can use the built-in ajaxOnly filter as follows:

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

After adding this, ones who try to use the getQuote action directly will get an HTTP error: 400 Bad Request.

The second way is to detect if request is made through AJAX with a special request method. For example, if we want to show the standard 404 "Not found" page, we can do this as follows:

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

Similarly, we can use one action to serve both AJAX and non-AJAX responses:

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,
            ),
        ),
    ),
    
    // ...
);
Further reading

For further information, refer to the following URLs:

  • http://api.jquery.com/
  • http://docs.jquery.com/Ajax/jQuery.ajax#options
  • http://www.yiiframework.com/doc/api/CHtml#ajax-detail
  • http://www.yiiframework.com/doc/api/CHtml#ajaxButton-detail
  • http://www.yiiframework.com/doc/api/CHtml#ajaxSubmitButton- detail
  • http://www.yiiframework.com/doc/api/CHtml#ajaxLink-detail

See also

  • The recipes named Managing assets and Working with JSON in this chapter
  • The recipe named Passing configuration from PHP to JavaScript in this chapter>

Managing assets

An ability to manage assets is one of the greatest parts of Yii. It is especially useful in the following cases:

  • When you want to implement an extension that stores its JavaScript, CSS, and images in its own folder and is not accessible from a browser
  • When you need to pre-process your assets: combine JavaScript, compress it, and so on
  • When you use assets multiple times per page and want to avoid duplicates

While the first two cases could be considered as bonus ones, the third one solves many widget reusing problems.

Let's create a simple Facebook event widget which will publish and use its own CSS, JavaScript, and an image.

Getting ready

  • Create a fresh Yii application using yiic webapp as described in the official guide
  • Check that assets directory under application's webroot (where index.php is) has write permissions; assets will be written there
  • Generate and download a preloader image from http://ajaxload.info/

How to do it...

Let's do some planning first. In Yii, you can place your widgets virtually inside any directory and often, it is protected/components. It is acceptable to have one or two classes inside, but when the number of classes increases, it can create problems. Therefore, let's place our widget into protected/extensions/facebook_events. Create an assets directory inside the widget and put inside the ajax-loader.gif you have just downloaded. Also, create facebook_events.css and facebook_events.js in the same directory.

  1. Therefore, let's start with the widget class itself 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");
        
        // Publishing and registering JavaScript file
        $cs->registerScriptFile(
            Yii::app()->assetManager->publish(
                $assetsDir.'/facebook_events.js'
            ),
            CClientScript::POS_END
        );
        
        // Publishing and registering CSS file
        $cs->registerCssFile(
            Yii::app()->assetManager->publish(
                $assetsDir.'/facebook_events.css'
            )
        );
        
        // Publishing image. publish returns the actual URL
        // asset can be accessed with
        $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. Now let's define body view we are using inside run method 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. We will need to put the following into 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. Write the following in facebook_events.css created previously:
.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. That is it. Our widget is ready. Let's use it. Open your protected/views/site/ index.php and add the following code to it:
<?php $this->widget("ext.facebook_events.EFacebookEvents", array(
    'keyword' => 'php',
))?>
 
<?php $this->widget("ext.facebook_events.EFacebookEvents", array(
    'keyword' => 'jquery',
))?>
  1. Now, it is time to check our application homepage. There should be two blocks with Facebook events named php events and jquery events, as shown in the following screenshot:

How it works...

When we use $this->widget in the site/index view, two EFacebookEvents methods are run: init which publishes assets and connects them to the page, and run which renders widget HTML. First, we use CAssetManager::publish to copy our file into the assets directory visible from the web. It returns URL that can be used to access the resource. In the case of JavaScript and CSS, we use CClientScript methods that add necessary <script> and <style> tags and prevent duplication. As for an image, we pass its URL to the body view that uses it to render a placeholder with CHtml::image. When JavaScript is loaded, it makes requests to Facebook API (described at http://developers.facebook.com/docs/api) and replaces placeholder with the actual data received.

There's more...

There is more about working with assets.

What is inside the assets directory?

Let's check our assets directory. It should look similar to the following:

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

Directories such as 1a6630a0 are used to prevent collisions of files with similar names from different directories. The name of the directory is a hash of complete path to the published asset directory. Therefore, assets from the same directory are copied to the same place. This means that if you publish both the image and the CSS file, you can reference images from CSS using relative paths.

Publishing an entire directory

Using CAssetManager::publish, you can publish an entire directory recursively. The difference is that single files are monitored after being published, whereas directories are not.

Further reading

For further information, refer to the following URL:

  • http://www.yiiframework.com/doc/api/CAssetManager
  • http://www.yiiframework.com/doc/api/CClientScript
  • http://www.yiiframework.com/doc/api/CHtml#asset

See also

  • The recipe named Creating a widget in Chapter 8, Extending Yii

Including resources into the page

Yii has a special class named CClientScript that can help to include scripts, CSS, and other resources into the page.

How to do it...

We will start with including a script. There are three types of scripts, namely, external scripts, core scripts, and inline scripts.

  1. External script is a script located in a file and accessible through its own URL. For example, to include a script with URL http://example.com/js/main.js, you can use the following code:
Yii::app()->clientScript->registerScriptFile("http://example.com/js/main.js");
  1. In order to control the place where script will be inserted, you can pass one of the following constants as the second parameter:
CClientScript::POS_HEADIn the head section right before the title element
CClientScript::POS_BEGINAt the beginning of the body section
CClientScript::POS_ENDAt the end of the body section
  1. Core scripts are the ones bundled with Yii, such as jQuery. You can include a core script in the following way:
Yii::app()->clientScript->registerCoreScript('jquery');
  1. All packages are listed in 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. In this list, keys of the array such as jquery, yii, yiitab, yiiactiveform are the names you can use with registerClientScript and the corresponding arrays are the real script file names relative to framework/web/js/source that are actually loaded.
  1. Inline scripts are the scripts contained in a page body. Typically, these are ones different at every request. You can include these type of scripts in the following way:
Yii::app()->clientScript->registerScript('myscript', 'echo "Hello, world!";', CClientScript::POS_READY);

The first parameter is the unique script ID you have chosen. The second parameter is the script itself. The third one tells Yii where to include a script. Additionally there are two more positions to ones used for registerScriptFile:

CClientScript::POS_LOADIn the window.onload() function
CClientScript::POS_READYIn the jQuery's ready function

Now, let's move on to CSS. There are two types of CSS: inline and external (no core type this time).

  1. In order to include an external CSS, you can use the following code:
Yii::app()->clientScript->registerCssFile ('http://example.com/css/main.css');

Additionally, this method takes a second parameter which allows you to specify which media type you want to include CSS for, such as screen or print. There is no position parameter, as the only valid place to include a CSS is inside a head tag.

Inline CSS should be avoided wherever possible, but in case you really need it, you can include it in the following way:

Yii::app()->registerCss('myCSS', 'body {margin: 0; padding: 0}', 'all');
  1. In the preceding code, myCSS is the unique ID. Then, we have the actual CSS code and a media type.

How it works...

The Yii's CClientScript methods which we have reviewed do not include scripts and CSS instantly. Instead, resources are stored until application controller calls the render method. Then, it finds a proper place in layout and inserts all needed script and CSS blocks and tags. If we include a resource with the same name or URL twice, then it will be included only once. This allows us to efficiently use resources in a widget, view partial, or any reusable piece of code.

There's more...

We have reviewed the most common resource types: JavaScript and CSS. However, there is more.

Using custom script packages

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

Registering linked resources

CClientScript offers another method that allows you to register a custom

Yii::app()->clientScript->registerLinkTag(
    'alternate',
    'application/rss+xml',
    $this->createUrl('rss/articles')
);
Registering meta tags

In addition, CClientScript allows registering of meta tags such as description or keywords by using the registerMetaTag method. For example, in order to specify a document encoding, you can use the following code:

Yii::app()->clientScript->registerMetaTag(' text/html;charset=utf-8', null, 'Content-Type');
Further reading

For further information, refer to the following URLs:

  • http://www.yiiframework.com/doc/api/CClientScript
  • http://www.yiiframework.com/doc/guide/1.1/en/topics.performance

See also

  • The recipe named Managing assets in this chapter
  • The recipe named Loading a block through AJAX in this chapter

Working with JSON

JSON is a very simple, compact, and therefore a widely used format for AJAX applications data exchange. Yii has a few handy ways to work with it. Therefore, let's create a simple application that will show news list and update it every two seconds.

Getting ready

  1. Create a new application by using the yiic webapp tool.
  1. Create and setup a new database.
  1. Add a table named news with at least id, created_on, and title fields:
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. Generate a News model using Gii.

How to do it...

  1. Create a new controller named protected/controllers/NewsController.php as follows:
<?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. Moreover, create a view named protected/views/news/index.php as follows:
<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. Now, run the index action of the news controller and try to add a few records into the news database table by running the addRandomNews action. Do not refresh the index page. News added will appear on the index page once every two seconds, as shown in the following screenshot:

How it works...

The index action does nothing special. It simply renders a view that includes a div container and some JavaScript code. As we are using jQuery, we need to ensure it is included:

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

Then, we define a function named updateNews and run it every 2,000 milliseconds using the core JavaScript setInterval function. In updateNews, we make an AJAX request to the data action of the same controller, to process the JSON response and replace the current content of placeholder div with formatted data.

In actionData, we get the latest news and convert them to JSON format by passing the result to CJSON::encode.

There's more...

For further information, refer to the following URLs:

  • http://api.jquery.com/category/ajax/
  • http://www.yiiframework.com/doc/api/CJSON/
  • http://www.yiiframework.com/doc/api/CClientScript/#registerCore Script-detail

See also

  • The recipe named Loading a block through AJAX in this chapter

Passing configuration from PHP to JavaScript

You can store application parameters in your configuration file protected/config/ main.php that we can access using Yii::app()->params['paramName']. When your application uses the JavaScript code, it is handy to have these parameters available for it. Let's see how to do it in a simple and effective way.

Getting ready

  1. Set up a fresh application using the yiic webapp tool. It should generate application parameters array in protected/config/main.php:
'params'=>array(
    // this is used in contact page
    'adminEmail'=>'webmaster@example.com',
),
  1. Add additional parameters as follows:
'params'=>array(
    // this is used in contact page
    'adminEmail'=>'webmaster@example.com',
    'alert' => array(
        'enabled' => true,
        'message' => 'Hello there!',
    ),
),

How to do it...

  1. Create a controller named protected/controllers/AlertController.php as follows:
<?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. Moreover, create view named protected/views/alert/index.php as follows:
<script>
if(config && config.alert && config.alert.enabled &&
    config.alert.message){
    alert(config.alert.message);
}
</script>
  1. Now, if you will run the alert controller index action, you should get a standard JavaScript alert window saying Hello there!, as shown in the following screenshot:

How it works...

We use CJavaScript::encode that converts PHP data structures into JavaScript ones to turn Yii application parameters into a JavaScript array. Then, we register a script that assigns it to a global variable config. Then, in our view JavaScript code, we just use this global variable config.

See also

  • The recipe named Managing assets in this chapter
  • The recipe named Loading a block through AJAX in this chapter

Handling variable number of inputs

Sometimes an application requires a form with variable number of inputs. For example, a task management application can provide a screen where you can add one or more tasks to your task list. An example of such an application is shown in the following screenshot:

By default, the page will display one task and two buttons: Add task will add another empty task and Save will reload a form with all the tasks added. Let's check how we can solve it.

Getting ready

Create a fresh application using yiic webapp.

How to do it...

For our example, we will not save any data into the database. Instead, we will learn how to get a form with variable number of fields up and running, and how to collect data submitted with it.

  1. Therefore, we will start with the task model. As we agreed not to use database, CFormModel will be enough. protected/models/Task.php:
<?php
class Task extends CFormModel
{
    public $title;
    public $text;
    public function rules()
    {
        return array(
            array('title', 'required'),
            array('text', 'safe'),
        );
    }
}
  1. Now, the controller 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. Now, the pretected/views/task/index.php view:
<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. Finally, a partial 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. That is it. Now run the index action of the task controller and check it in action, as shown in the following screenshot:

How it works...

Let's review how it works starting from the controller's index action. As we are working with more than one data item, we need to collect them accordingly. Same as with a single model, a form will pass its data in $_POST['model_name']. The only difference is that in our case, there will be an array of data items as follows:

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

For each data item, we are creating a Task model, setting its attributes with data item and if it is valid, storing a model into the $models array as follows:

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();

If the $models array is not empty, then there is data passed from the form and it is valid. If there is no data in the $models, then we still need at least one item to show it in the form.

We are not actually saving data in this example. When we use Active Record models, we can actually save data using $model->save().Then, we just render the index view when we render a form as follows:

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

As there are many models, we need to render fields for each one. That is done in the _task view partial:

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

Note how we use active labels and active fields. For each one, we specify a model and a name in format [model_index]field_name. The preceding code will generate the following 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>

Fields such as Task[0][title] are a special PHP feature. When submitted, parameters with such names are automatically parsed into PHP arrays.

Now, let's review how the Add task button works:

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

We are adding a button with a class tasks-add and a jQuery script that attaches the onClick handler to it. On click, we send an AJAX request to the field action of our controller with parameter index. Parameter's value equals the number of data items in the form. The field action responds with a part of an HTML form that we append to what we already have.

There's more...

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

See also

  • The recipe named Loading a block through AJAX in this chapter
评论 X

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