Fully ajax website with Yii — Part 1

This article will be suitable for who made fully ajax site like  Gmail with best PHP Framework — Yii.
Main requirements are:

  1. All pages should be loaded via ajax.
  2. All forms should be submitted via ajax.
  3. Pages should be reachable by search engines.
  4. History support.
  5. Window title support
  6. Minimal additional code.

Here  is my solution.
I get for example simple web application which you can made with help of yiic utility.

cd /home/www/mywebsite
wget http://yii.googlecode.com/files/yii-1.1.7.r3135.tar.gz
gunzip yii-1.1.7.r3135.tar.gz
tar -xvf yii-1.1.7.r3135.tar
mv yii-1.1.7.r3135/framework/ ./
php framework/yiic.php webapp ./

That is it your application has been created!

Add this code to the protected/views/layout/main.php file before  </head> tag.

 
<?php 
		$cs = Yii::app()->getClientScript();
  		$cs->registerCoreScript('jquery');
  		$cs->registerCoreScript('bbq');
?>
 
  	<?php 
$script=<<<HTML
			function applychanges(obj) {
					for(k in obj) {
						jQuery(k).html(obj[k]);
					}	
			}
HTML;
$cs->registerScript('applychanges', $script, CClientScript::POS_HEAD);
?>	
 
  	<?php 
$script=<<<HTML
		var headline = $("#loading");
 
		$(document).ajaxSend(function() {
    		headline
        	.attr("class", "activity")
		});
 
		$(document).ajaxStop(function() {
    		headline.removeAttr("class")
		 	});
HTML;
$cs->registerScript('loading-indicator', $script, CClientScript::POS_READY);
?>
 
 
 
<?php 
$script=<<<HTML
 
  // handling links and forms
 
  $("a:not(.direct)").live("click", function(){		  	
		    var href = $(this).attr( "href" );		    
		    if(href=="#")
		    	return false;	
 
		    $.bbq.pushState({ url: href});  
		    return false;
		  });
 
 
 
		  $("form:not(.direct)").live("submit", function(){
		  	var url = "";
 
			var type = jQuery(this).attr("method");
 
			if(type==undefined)
		  		type = "get";
 
		  	if(type=="get") {
 
		  		var action = jQuery(this).attr("action");
		  		if(action.indexOf("?")==-1)
		  			url = action + "?" + jQuery(this).serialize();
		  		else 
		  			url = action + "&"	+ jQuery(this).serialize();
 
		  		$.bbq.pushState({ url: url});
 
		  	} else {
		  		jQuery.ajax({
					type:"post",
					data:$(this).serialize(),
					url:jQuery(this).attr("action"),
						success:applychanges
					});
				}	
 
 
		  	return false;
		  })
 
 
		   $(window).bind( "hashchange", function(e) {	    
    		var url = $.bbq.getState( "url" );    		
 
    		if(!url)
    			return;
 
		    $("a").each(function(){
		      var href = $(this).attr( "href" );
 
		      if ( href === url ) {
		      	$(this).addClass( "current" ); $(this).parent("li").addClass( "active" );
 
		      } else {
		        $(this).removeClass( "current" ); $(this).parent("li").removeClass( "active" );
		      }
		    });
 
				jQuery.ajax({
					type:"get",
					url:url,
					success:applychanges
				});
 
		  });
 
		  $(window).trigger( "hashchange" );
 
HTML;
$cs->registerScript('ajaxlinks-and-forms', $script, CClientScript::POS_READY);
?>

Next code should be added after <body> tag

<div id="loading"><?php echo Yii::t('main','Loading');?></div>

We’ve jus added javascript code which will handle all client side job
Then we extend Base controller class «Controller».

protected/componens/Controller.php; Add new methods below:

public function ajaxRender($file, $data=array()) {
 
		$data['title'] = CHtml::encode($this->pageTitle);			
		header('Content-type: text/x-json');
		echo CJSON::encode($data);
		Yii::app()->end();
 
	}
 
	public function render($file, $params = array(), $data=array()) {
 
		if(Yii::app()->request->isAjaxRequest){
 
			if(Yii::app()->user->hasFlash('updatedata')) {
				$flashdata = Yii::app()->user->getFlash('updatedata');		
				$data = $data + $flashdata;	
			}
 
 
			$data['#content'] = parent::renderPartial($file, $params, true);
			$data['title'] = CHtml::encode($this->pageTitle);
 
			header('Content-type: text/x-json');
			echo CJSON::encode($data);
			Yii::app()->end();
 
		} else {			
			echo parent::render($file, $params, true);	
		}	
 
	}
 
	public function redirect($url) {
 
		if(is_array($url) && isset($url[0]))
			$url = $url[0];
 
		if(Yii::app()->request->isAjaxRequest){
 
			$data = array();
 
			if(Yii::app()->user->hasFlash('updatedata')) {
				$flashdata = Yii::app()->user->getFlash('updatedata');		
				$data = $data + $flashdata;	
			}
 
			$data['#content']= '<script type="text/javascript">
 
				jQuery.ajax({
					url:"'.$url.'",
					type:"get",
					success:applychanges
				});
 
			</script>';
 
 
 
			header('Content-type: text/x-json');
			echo CJSON::encode($data);
			Yii::app()->end();
 
		} else {
			parent::redirect($url);
		}
	}
 
	public function refresh() {
		if(Yii::app()->request->isAjaxRequest){
 
			$data = array();
 
			if(Yii::app()->user->hasFlash('updatedata')) {
				$flashdata = Yii::app()->user->getFlash('updatedata');		
				$data = $data + $flashdata;	
			}
 
 
			$data['#content']= '<script type="text/javascript">
			var url = $.bbq.getState( "url" );
 
			jQuery.ajax({
					url:url,
					type:"get",
					success:applychanges
				});
 
			</script>';
 
			header('Content-type: text/x-json');
			echo CJSON::encode($data);
			Yii::app()->end();
 
		} else {
			parent::refresh();
		}
	}

Then you have to override some Request methods to support CWebUser redirects:

Put content below to new file with name protected/components/EHttpRequest.php

<?php 
 
class EHttpRequest extends CHttpRequest
{
 
	public function redirect($url,$terminate=true,$statusCode=302) {
		if(is_array($url) && isset($url[0]))
			$url = $url[0];
 
		if(Yii::app()->request->isAjaxRequest){
 
			$data = array();
 
			if(Yii::app()->user->hasFlash('updatedata')) {
				$flashdata = Yii::app()->user->getFlash('updatedata');		
				$data = $data + $flashdata;	
			}
 
			$data['#content']= '<script type="text/javascript">
 
				jQuery.ajax({
					url:"'.$url.'",
					type:"get",
					success:applychanges
				});
 
			</script>';
 
			header('Content-type: text/x-json');
			echo CJSON::encode($data);
 
			Yii::app()->end();	
		} else {
			parent::redirect($url);
		}
	}
 
	public function refresh() {
		if(Yii::app()->request->isAjaxRequest){
 
			$data = array();
 
			if(Yii::app()->user->hasFlash('updatedata')) {
				$flashdata = Yii::app()->user->getFlash('updatedata');		
				$data = $data + $flashdata;	
			}
 
 
			$data['#content']= '<script type="text/javascript">
			var url = $.bbq.getState( "url" );
 
			jQuery.ajax({
					url:url,
					type:"get",
					success:applychanges
				});
 
			</script>';
 
			header('Content-type: text/x-json');
			echo CJSON::encode($data);
 
 
			Yii::app()->end();	
		} else {
			parent::refresh();
		}
	}
}

Set new class name for request component in your main config protected/config/main.php

It should looks like this

  .... 
  'components'=>array(
		'user'=>array(
			// enable cookie-based authentication
			'allowAutoLogin'=>true,
		),
 
		'request'=>array(
                     'class'=>'EHttpRequest',
               ),
 
   ....

That’s all. Now you have full ajax simple web application.

Offcourse it has some limitations and issues, further I’ll extend this article.

P.S. I have forgotten to say about styles.
Just add these lines to your css file.

#loading {
	position:fixed;
	padding:3px;
	background:#80B646;
	color:#fff;
	display:none;
	z-index:999;
	right:0;
	top:0;
}
#loading.activity {
	display:block;
}