Yii 1.1: Javascript and AJAX with Yii

33 followers

This page intends to provide an exhaustive guide of how to use Javascript (JS) in Yii. It does not explain how to learn coding in JS, but how to deal with it "the Yii way".

  • The first part describes cases where JS is almost hidden in Yii.
  • The second part describes how to write custom JS code.

1. Official JS wrappers

Yii often uses Javascript, even when the developper does not explicitly ask for it. The framework has chosen the library JQuery which is included in Yii's distribution. JQuery is regularly updated, like the other JS libraries that Yii provides. It is not recommended to load another jQuery library, there is a high risk of conflict.

1.1 Form validation

This is a case where JS can be almost invisible, though it is disabled by default (in Yii 1.1.11). There are two kinds of validations that use JS:

  • client-side validation,
  • AJAX validation.

See the API documentation of CActiveForm for explanations and examples.

1.2 CGridView

By default, the scaffolding tool Gii builds "admin" pages with CGridView and "index" pages with CListView. The surprise is that CGridView and CListView use AJAX by default. If you need to customize this, there are several parameters in the API.

There are pros and cons for using AJAX by default. The main argument against this default behaviour is that the user actions don't appear in the browser navigation history: an user can't go back to the previous search filters. If this drawback makes you want to disable AJAX in CGridView, then init the widget with the parameter 'ajaxUpdate' => false.

1.3 CJui* classes

The easiest way to use Javascript from Yii is to use Yii classes. The jQuery UI plugins have been wrapped in PHP classes. You can consult the list of these classes. Each documentation page begins with an example.

There are a few other JS classes among the web widgets of Yii, notably CTreeView.

1.3.1 Passing JS code to a PHP class (CJuiAutoComplete)

In many cases, the basic examples of the CJui classes aren't enough. There is often a need to provide custom JS actions.

For example, we'll consider CJuiAutoComplete. We'd like to configure an instance with 2 main features:

  • The completion candidates are found through AJAX,
  • The ID of a selected candidate is added to the form.
AJAX source and dynamic update of the Yii HTML form

The PHP configuration of CJuiAutoComplete is an associative array. Its "source" key must be associated to AJAX, that means its value should be a JS function. We can't write it simply as "function()…" because it would be interpreted as a string value. The right syntax is: "js:function(request, response) {…". The "js:" prefix tells Yii that what follows is pure JS code and should not be escaped.

The principle is the same for updating the form: from within PHP, we pass a JS function that will read the item chosen. Once again, the syntax will be: 'select' => "js:function(…".

1.3.2 The complete example

Here is a user selection where the interface shows only names but the form transmits a numerical ID.

echo $form->hiddenField($model, 'userId');
 
$quotedUrl = CJavaScript::encode($this->createUrl(array('user/complete')));
$params = array(
    'name' => "userComplete",
    'source' => 'js:function(request, response) {
        $.ajax({
            url: "'. $quotedUrl . '",
            data: { "term": request.term, "fulltext": 1 },
            success: function(data) { response(data); }
        });
}',
    // additional javascript options for the autocomplete plugin
    // See <http://jqueryui.com/demos/autocomplete/#options>
    'options' => array(
        'minLength' => '3', // min letters typed before we try to complete
        'select' => "js:function(event, ui) {
            jQuery('#MyModel_userId').val(ui.item.id);
            return true;
}",
    ),
);
$this->widget('zii.widgets.jui.CJuiAutoComplete', $params);

This code outputs a hidden form field that will hold the selected user ID. It is updated in the select function, using its HTML id. Of course, this ID depends on the model name. It is usually "ModuleName_AttributeName" but you should inspect you HTML form to confirm this. A smarter code could use CHtml::resolveNameID() to compute this id.

A few notes that will be detailed later:

  • In the ajax parameter, "data" should not be a string like "fulltext=1&term="+request.term.
  • If you need to mix in PHP values in this "data", then use CJavaScript::encode().
  • The URL of the AJAX call is built in PHP since it is the only portable solution.

Here is an example of the webservice that answers the completion request.

/**
 * Propose completions for a term (AJAX).
 */
public function actionAjaxComplete()
{
    if (!YII_DEBUG && !Yii::app()->request->isAjaxRequest) {
        throw new CHttpException('403', 'Forbidden access.');
    }
    if (empty($_GET['term'])) {
        throw new CHttpException('404', 'Missing "term" GET parameter.');
    }
    $term = $_GET['term'];
    $filters = empty($_GET['exclude']) ? null : (int) $_GET['exclude']);
    header('Content-Type: application/json; charset="UTF-8"');
    echo json_encode(User::completeTerm($term, $exclude));
    Yii::app()->end();
}

The important lines read the GET "term" parameter, send the JSON header, and encode the result in JSON. If the charset of your application is not UTF-8, then you should replace json_encode() with the slower Yii static method CJson::encode(). In the code above, the static method User::completeTerm() is supposed to return an array of rows that have the keys: "id", "value", "label".

1.4 Partial update with AJAX and CHtml

There are two static methods in Yii that

The following code will replace the content of the HTML element of ID "my-profile" with the output of a call to the action "ajaxcontent" of the controller "profile".

echo CHtml::ajaxLink(
    'Update profile',
    array('profile/ajaxcontent', 'id' => $id), // Yii URL
    array('update' => '#my-profile') // jQuery selector
);

Of course, in this situation, the action "profile/ajaxcontent" must output HTML, though not a full HTML page. If you prefer returning structured data and parsing it in Javascript, then replace the "update" attribute by a "success" one. An example:

// the data received could look like: {"id":3, "msg":"No error found"}
array('success' => 'js:function(data) { $("#newid").val(data.id);
    $("#message").val(data.msg); }')

The easiest way to output JSON is to convert a PHP structure using CJson::encode(). See the AJAX example above for a more detailed example.

1.5 Extensions that wrap JS into PHP classes

Aside from the official Yii classes, many extension provide JS features. Some extensions are just wrappers that try to ease the integration of a JS plugin. Please consult the JS extensions list if you are looking for a specific feature.

2. Writing custom JS code

Before writing your own custom code, don't forget to check if there isn't a PHP wrapper that suits your needs:

2.1 Requiring some JS libraries (jQuery and such)

Some JS libraries are distributed with Yii. They are load automatically when the PHP code needs them. If you want to make sure they are loaded, you need to add the following code:

// Load jQueryUI (and also jQuery which is required by jQueryUI)
Yii::app()->clientScript->registerCoreScript('jquery.ui');

This will put the file into the assets of the application (the file is under "yii/framework/web/js/source"), and load it through a script tag. By default, CClientScript::registerCoreScript() will load it at the end of the page. Registering a script twice has no effect.

2.2 Inline JS (embedded in the HTML page)

The short snippets of JS code can be written inside a PHP string. They will be appended inside a <script> tag.

Yii::app()->clientScript->registerScript('uniqueid', 'alert("ok");');

For a longer code, the lack of syntax highlighting could be really painful. The inline source could be replaced by a file content:

// raw JS file expanded into the page
Yii::app()->clientScript->registerScript('uniqueid', file_get_contents('js/mycode.js'));
 
// JS file with embedded PHP code
ob_start();
include 'js/mycode.js';
Yii::app()->clientScript->registerScript('uniqueid', ob_get_clean());

2.3 JS in an external file

Of course, modifying the layout template is an option when a JS will always be linked. But when the JS file is loaded only on some requests, then Yii provides a method for this.

// Load a file with an aboslute and external URL
Yii::app()->clientScript->registerScriptFile('http://example.com/a.js');
 
// Load a file from "js/custom.js" under the root of the application
Yii::app()->clientScript->registerScriptFile(Yii::app()->baseUrl . '/js/custom.js');

See the API doc of CClientScript::registerScriptFile() to force where on the page to load the script. Please note that the same CClientScript is used to link other documents to the page (CSS, etc).

2.4 External JS file through assets

In some cases, the JS code cannot be in a public directory. This is the case when you develop an extension: every file will be under "protected/extensions". Then you must first instruct Yii to publish your JS code into the assets directory. Here is an exemple that publishes a whole directory.

// Put the local directory into the application's assets
$assetsUrl = Yii::app()->assetManager->publish(__DIR__ . '/myassets');
 
// Load a published file
Yii::app()->clientScript->registerScriptFile($assetsUrl . '/custom.js');

See the documentation of CAssetManager::publish() for options.

2.5 Inline code or external file?

Loading a JS file is usually to be preferred. There are many reasons for this, the main one being the readability: it is easier to work with a file of its own.

Yet, some tasks cannot be done purely in Javascript. For instance, there is no portable way to build a Yii URL from JS: the path depends on the configuration of CUrlManager. A solution is usually to put all the JS code in a file, and complete it with JS vars defined from PHP.

Yii::app()->clientScript->registerScriptFile(Yii::app()->baseUrl . '/js/custom.js');
$vars = array(
    'ajaxUrl' => $this->createUrl('complete', 'id' => $model->id,
);
Yii::app()->clientScript->registerScript('variables', 'var myApp = ' . CJavaScript::encode($vars) . ';');

Aside CJavaScript::encode(), the method CJavaScript::quote() can also be useful.

$url = $this->createUrl('app/ajaxProcessor');
$cs->registerScript('var1', "var myUrl = '" . $url . "';"); // can break with some URLs
$cs->registerScript('var1', "var myUrl = '" . CJavaScript::quote($url, true) . "';");

3. Final words

Though you can write Javascript in a Yii application without caring of the PHP framework, it has many drawbacks. For instance, the URL used by JS could become wrong at the first configuration change. Or some pages could break because of conflicts between the JS loaded by Yii and the JS loaded by the developper. Even if you choose not to use the wrappers Yii offers, you should still use:

Total 2 comments

#12551 report it
kokoseq at 2013/03/28 04:56am
Very helpfull

thank you very much!

#10057 report it
kalempir at 2012/10/02 07:01pm
!!!

Very good explanation. Thanks.

Leave a comment

Please to leave your comment.

Write new article