Difference between #5 and #4 of Javascript and AJAX with Yii

unchanged
Title
Javascript and AJAX with Yii
unchanged
Category
How-tos
unchanged
Tags
javascript, AJAX
changed
Content
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](http://www.jquery.com) 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](http://www.yiiframework.com/doc/api/1.1/CGridView#ajaxUpdate-detail).

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](http://jqueryui.com) plugins have been wrapped in PHP classes.
You can consult the list of [these
classes](http://www.yiiframework.com/doc/api/1.1/#zii.widgets.jui).
Each documentation page begins with an example.

There are a few other JS classes among [the web
widgets](http://www.yiiframework.com/doc/api/1.1/#system.web.widgets)
of Yii, notably [CTreeView](http://www.yiiframework.com/doc/api/1.1/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](http://www.yiiframework.com/doc/api/1.1/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()](http://php.net/manual/en/function.json-encode.php) 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',$cs->registerScript('var1',
"var myUrl = '" . $url . "';"); // can break with some
URLs
$cs->registerScript(var1',
$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()]