Yii Framework Forum: [Extension] Jii - Yii Framework Forum

Jump to content

Page 1 of 1
  • You cannot start a new topic
  • You cannot reply to this topic

[Extension] Jii Php to javascript models and variables encoder Rate Topic: ***** 1 Votes

#1 User is offline   cgabbanini 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 6
  • Joined: 04-December 12

Posted 05 December 2012 - 03:20 AM

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

0

#2 User is offline   cgabbanini 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 6
  • Joined: 04-December 12

Posted 06 December 2012 - 02:58 AM

I just have fixed some bugs. Sorry. Hope someone will find this useful.
0

#3 User is offline   yiqing95 

  • Master Member
  • PipPipPipPip
  • Yii
  • Group: Members
  • Posts: 602
  • Joined: 27-December 10
  • Location:china

Posted 06 December 2012 - 04:53 AM

nice idea! thanks for sharing .
and ... why not post it to extension repo or github . :lol:
0

#4 User is offline   cgabbanini 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 6
  • Joined: 04-December 12

Posted 06 December 2012 - 02:05 PM

View Postyiqing95, on 06 December 2012 - 04:53 AM, said:

nice idea! thanks for sharing .
and ... why not post it to extension repo or github . :lol:


Thank you so much!
Actually, Jii github repository is at: github.com/postypython/jii

I cannot post it to the extensions repo because I my user account is not allowed: I have too few posts, I'm sorry.
0

#5 User is offline   cgabbanini 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 6
  • Joined: 04-December 12

Posted 10 December 2012 - 03:41 PM

Jii is now available on Yii extensions repository at: http://www.yiiframew.../extension/jii/
1

#6 User is offline   cgabbanini 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 6
  • Joined: 04-December 12

Posted 17 December 2012 - 01:41 AM

@pmaselkowski about Ko mapping
Thank you for your suggestion.

I think that jii jsonizer behaves as much as you describe your converter does: it accepts objects or array of objects inheriting from CModel class on one side and on the other site it can parse CActiveDataProvider(s).
Each object is parsed checking first if the user has specifed any jsonableAttributes through the method getJsonizeables and, if non is found, each attribute is returned.
Then it checks if any relation has been loaded and, if so, it performs the necessary operations to extract the data.
The result is a simple array that will be encoded to JSON and added to jii.

So I think that Jii offers two ways of supporting ko.mapping plugin (which by the way I haven't still used or tested). I'd personally choose the second one.
The first is "server side" through Yii::app()->jii->jsonize that returns a JSON representation of your models:
<script type="text/javascript">
    ...
    // note that there are of course a few ways to achieve the following result
    ko.mapping.fromJS(<?php echo Yii::()->jii->jsonize($model); ?>);
    ...
</script>


The second is "client side" through jii.models.your_model.toJS() method.
In this case the model you need to assign to ko mapping is available as a Javascript object on the Jii Javascript object:
    ...
    ko.mapping.fromJS(jii.models.your_model.toJS());
    ...


That's it.
Anyway Jii jsonizer is still missing for lazy loaded relations: I am wondering if that could be useful or not.

@Cherif
I tried to simplify Jii jsonizer to make it more similar at what It would be if it was attached to a Model as a behavior:
<?php
// $model is automatically converted to JSON
Yii::app()->jii->addModel('your_model', $model);
?>

Then you can get your_model observable in the following way:
var ko_observable = jii.utils.observable(jii.models.your_model.getObservable());
var ko_observable_array = jii.utils.observableArray(jii.models.your_model.getObservableArray());


I am planning to add attribute validation. Do you think that it would be useful?
Otherwise, what do you think it could be useful to add?
:D
0

#7 User is offline   vova07 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 6
  • Joined: 18-January 13
  • Location:Chisinau, Moldova

Posted 19 January 2013 - 05:32 AM

Sorry, but why not :
<?php
class Jii extends CComponent
{
	public  $config;
	
	private $_jsonizer;
	
	private $_bindings	= array();

	private $_models 	= array();
	
	private $_params 	= array();
	
	private $_urls 		= array();

	private $_functions = array();
	
	private $_script 	= 'jii-min-0.0.4.js';

	public function init()
	{
		if (!isset($this->config['script'])) {
			$this->config['script'] = $this->_script;
		}

		// publish jii script
		$jii_js = Yii::app()->assetManager->publish(Yii::getPathOfAlias('common.extensions.jii.js') . DIRECTORY_SEPARATOR .  $this->config['script']);
		
		// registers jii
		Yii::app()->clientScript->registerScriptFile($jii_js, CClientScript::POS_END);
		
		$this->_jsonizer = new Jsonizer();
	}
	
	public function jsonize($models)
	{
		return $this->_jsonizer->jsonize($models);
	}

	/**
	* Adds a model to Jii
	* @param string $name the name of the jii object property
	* @param mixed $data the model to be added
	*/
	public function addModel($name, $data)
	{
		// if we cannot decode JSON $data, we try to jsonize
		if (json_decode($data) === null) {			 
			$data = $this->jsonize($data);
		}
		 
		$this->_models[$name] = $data;
	}

	public function addFunction($name, $code)
	{
		$this->_functions[$name] = $code;
	}

	/**
	* Allows to add custom function to be executed after document is ready
	* @params string $function javascript anonymous function
	*/
	public function addBindings($function)
	{		
		$this->_bindings[] = $function;
	}
	
	/**
	* Converts a Php variable into a Javscript one
	*/
	public function addParam($name, $value)
	{		 
		if (!is_array($value)) {
			
			$this->_params[$name] = $value;

		} else {
			
			if (is_object($value) || $this->_isAssoc($value)) {
	
				$this->_params[$name] = json_encode($value);
	
			} else {
				$this->_params[$name] = $value;
			}
		}
		

	}
	
	private function _toJsPrimitive($value)
	{
		return CJavaScript::encode($value);
	}
	
	private function _jsonEncode($value)
	{
		return CJavaScript::jsonEncode($value);
	}

	private function _isAssoc($arr)
	{
	    return array_keys($arr) !== range(0, count($arr) - 1);
	}
	public function addUrl($label, $url)
	{
		$this->_urls[$label] = htmlspecialchars($url);
	}
	
	/**
	 * The following to ensure that each script is rendered wherever you add it to your view
	 * @return string $javascript javascript code
	 */
	public function getScript()
	{
		$models = $params = $urls = $functions = $bindings = '';
		
		if(!empty($this->_params)) {
			$params = 'jii.params = ' . $this->_jsonEncode($data) .  ';';

		if(!empty($this->_urls)) {				
				$urls .= 'jii.urls = ' . $this->_jsonEncode($data) .  ';';
		}

		if (!empty($this->_functions)) {				
				$functions .= 'jii.functions = ' . $this->_jsonEncode($data) .  ';';
					
		}
		
		if (!empty($this->_models)) {
			foreach($this->_models as $name => $data) {				
				$models .= 'jii.models.' . $name .' = new jii.Model(' . $this->_jsonEncode($data) . ');';
			}
		}

		if (!empty($this->_bindings)) {
			foreach ($this->_bindings as $binding) {
				$bindings .= 'jii.bindings.bindings.push(' . $this->_jsonEncode($binding) .');' . PHP_EOL;
			}
		}
		
		// clears everything after each call
		$this->_params 		= array();
		$this->_urls 		= array();
		$this->_models 		= array();
		$this->_bindings 	= array();
		
		return $urls . PHP_EOL . $params . PHP_EOL . $functions . PHP_EOL . $models . PHP_EOL . $bindings;	
	}
}

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)
	{
		// support for CActiveDataProvider
		if ($data instanceof CActiveDataProvider) {
			$data = $data->getData();
		}
		
		if (is_array($data)) {
			return json_encode($this->_jsonize($data));
		} else {
			return json_encode($this->_jsonizeOne($data));
		}
		
	}	
		
}

Change code : public function addParam() and public function getScript()
0

#8 User is offline   Nur Rochim 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 42
  • Joined: 04-February 13
  • Location:Indonesia

Posted 26 March 2013 - 10:15 AM

please screen shoots demo aplications :)
0

Share this topic:


Page 1 of 1
  • You cannot start a new topic
  • You cannot reply to this topic

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users