Hi to all,
even though I’ve been using Yii for almost one year and a half, I am still pretty new to it’s forum.
Nevertheless I would like to give my contribution to its great community with an extension I would like to share.
It’s purpose is to help you keeping your code more maintainable and, in particular, to keep the clearest separation between PHP and Javascript code.
Jii extension allows you to share variables and models from your server side PHP code with your client side Javascript code.
Each variabile is encoded using CJavaScript::encode and each model - with it’s relations - (or models, or any instance inheriting from CModel) is converted to its JSON equivalent.
Moreover, for security reason, you can decide with attributes will be available on the client side.
Anyway below you will find copy and paste code, configuration instructions and a simple how-to guide.
Feel free to use it and to report any bug you find as well as feature requests or comments.
Thank you
How to use Jii:
// adding params
Yii::app()->jii->addParam('integer', 10);
Yii::app()->jii->addParam('unsigned_integer', -10);
Yii::app()->jii->addParam('unsigned_float', 451.239873);
Yii::app()->jii->addParam('signed_float', -309.0092927);
Yii::app()->jii->addParam('bool_false', false);
Yii::app()->jii->addParam('bool_true', true);
Yii::app()->jii->addParam('string', '<h1>Title</h1><a href="#">link</a>');
Yii::app()->jii->addParam('associative_array', array('goofy' => 3409879, '+349287//' => '<a>link</a>'));
Yii::app()->jii->addParam('numeric_array', array(0, 1, -39, -938.2223, '<a href="#">Test</a>', true));
Yii::app()->jii->addParam('object', $object);
// adding urls
Yii::app()->jii->addUrl('view_test_url', $this->createUrl('test/view', array('id' => 1)));
// adding functions
Yii::app()->jii->addFunction('function', 'function(){ alert("This is an alert!"); }');
How to add Jii to your page:
Yii::app()->clientScript->registerScript('jii', Yii::app()->jii->getScript(), CClientScript::POS_END);
How to add models to Jii:
$jsonized_model = Yii::app()->jii->jsonize($model);
// adding models to jii
Yii::app()->jii->addModel('model', $jsonized_model);
You can define which attributes will be jsonized by adding the following method to your model classes:
...
public function getJsonizeables()
{
return array(
'attribute_name_1',
'attribute_name_3',
'attribute_name_4',
);
}
...
Then, from your Javascript code, Jii will be available as an object on the global scope with the following properties:
var Jii = {
params: {},
models: {},
urls: {},
functions: {},
}
How to configure Jii:
'components' => array(
...
'jii' => array(
'class' => 'Jii',
),
...
),
Create a Jii.php file under protected/components and paste the following code:
<?php
class Jii extends CComponent
{
private $_jsonizer;
private $_obj = 'var Jii = {params: {{params}}, models: {{models}}, urls: {{urls}}, functions: {{functions}}}';
private $_models = array();
private $_params = array();
private $_urls = array();
private $_functions = array();
public function init()
{
$this->_jsonizer = new Jsonizer();
}
public function jsonize($models)
{
return $this->_jsonizer->jsonize($models);
}
public function addModel($name, $data)
{
$this->_models[$name] = $data;
}
public function addFunction($name, $code)
{
$this->_functions[$name] = $code;
}
/**
* Converts a Php variable into a Javscript one
*/
public function addParam($name, $value)
{
if (is_object($value) || $this->_isAssoc($value)) {
$this->_params[$name] = json_encode($value);
} else {
if (!is_array($value)) {
$this->_params[$name] = $this->_toJsPrimitive($value);
} else {
$array = '[{items}]';
$items_string = '';
foreach ($value as $item) {
$items_string .= $this->_toJsPrimitive($item) . ',';
}
$items_string = substr($items_string, 0, -1);
$array = str_replace('{items}', $items_string, $array);
$this->_params[$name] = $array;
}
}
}
private function _toJsPrimitive($value)
{
return CJavaScript::encode($value);
}
private function _isAssoc($arr)
{
return array_keys($arr) !== range(0, count($arr) - 1);
}
public function addUrl($label, $url)
{
$this->_urls[$label] = '"' . htmlspecialchars($url) . '"';
}
public function getScript()
{
$models = $params = $urls = $functions = '';
if (!empty($this->_params)) {
foreach($this->_params as $name => $data) {
$params .= "$name: " . $data . ',' . PHP_EOL;
}
$params = substr($params, 0, -2);
}
if (!empty($this->_models)) {
foreach($this->_models as $name => $data) {
$models .= "$name: " . $data . ',' . PHP_EOL;
}
$models = substr($models, 0, -2);
}
if (!empty($this->_urls)) {
foreach($this->_urls as $name => $data) {
$urls .= "$name: " . $data . ',' . PHP_EOL;
}
$urls = substr($urls, 0, -2);
}
if (!empty($this->_functions)) {
foreach($this->_functions as $name => $code) {
$functions .= "$name: " . $code . ',' . PHP_EOL;
}
$functions = substr($functions, 0, -2);
}
$this->_obj = str_replace(array('{models}', '{params}', '{urls}', '{functions}'), array($models, $params, $urls, $functions), $this->_obj);
return $this->_obj;
}
}
class Jsonizer
{
/**
* Converts a CActiveRecord instance into an array
* @param CActiveRecord $model
* @return array $model
*/
private function _jsonizeOne($model)
{
// for each model we store only jsonizeables attributes
$attributes = array();
$jsonizeables = array();
// we select which attributes must be jsonized
if (method_exists($model, 'getJsonizeables')) {
$attributes = $model->getJsonizeables();
// we get all model attributes if no jsonizeables attributes have been found
} else {
$attributes = array_keys($model->getAttributes());
}
// we encode each attribute into a javascript variable
foreach ($attributes as $attribute_name) {
$jsonizeables[$attribute_name] = $model->$attribute_name;
}
// basic inheritance detection
if ($this->_isParent('CModel', $model)) {
$modelArray = $jsonizeables;
if (method_exists($model, 'relations')) {
$relations = array_keys($model->relations());
foreach ($relations as $relation) {
if ($model->hasRelated($relation)) {
$related_models = $model->getRelated($relation);
if ($related_models !== null) {
if (is_array($related_models)) {
if (!empty($related_models)) {
foreach($related_models as $related) {
$modelArray[$relation][] = $this->_jsonizeOne($related);
// print_r($this->_jsonizeOne($related));
}
} else {
$modelArray[$relation][] = array();
}
} else {
// print_r($related_models->getAttributes());
$modelArray[$relation] = $this->_jsonizeOne($related_models);
}
} else {
$modelArray[$relation] = null;
}
}
}
}
}
// print_r($modelArray);
return $modelArray;
}
/**
* Converts an array of CActiveRecord instances into a php array
* @param array $models
* @return array
*/
private function _jsonize($models)
{
$modelArray = array();
$i = 0;
foreach ($models as $model) {
$model->getAttributes();
$modelArray[$i++] = $this->_jsonizeOne($model);
}
return $modelArray;
}
private function _isParent($classname, $child)
{
while(($parent = get_parent_class($child)) !== false) {
if ($parent === $classname) {
return true;
}
$child = $parent;
}
return false;
}
/**
* Converts CModel instances into JSON objects
* @param CModel $data
* @return string $json
*/
public function jsonize($data)
{
if (is_array($data)) {
return json_encode($this->_jsonize($data));
} else {
return json_encode($this->_jsonizeOne($data));
}
}
}