Javascript and AJAX with Yii

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.

~ `php 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.

~ `php /**

  • 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

  • [CHtml::ajaxLink()]
  • [CHtml::ajaxbutton()]

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

~ `php 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:

~ `php // 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](http://www.yiiframework.com/extensions/?tag=javascript)
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:

* [JUI Widgets](http://www.yiiframework.com/doc/api/1.1/#zii.widgets.jui)
* [Web Widgets](http://www.yiiframework.com/doc/api/1.1/#system.web.widgets)
* [JS extensions](http://www.yiiframework.com/extensions/?tag=javascript)

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

~
```php
// 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.

~
```php
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:

~
```php
// 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.

~
```php
// 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.
~
```php
// 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.

~
```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.
~
```php
$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:

* [CClientScript::registerCoreScript()]
* [CClientScript::registerScriptFile()]
* [CClientScript::registerScript()]

23 0
30 followers
Viewed: 182 239 times
Version: 1.1
Category: How-tos
Written by: François Gannaz
Last updated by: François Gannaz
Created on: Sep 30, 2012
Last updated: 11 years ago
Update Article

Revisions

View all history

Related Articles