I just received a copy of Yii 1.1 Application Development Cookbook from Packt, and I’m already done reading it!. It’s a great book if you are working with the Yii Framework. Stay tuned for the full review!

  • Learn to use Yii more efficiently through plentiful Yii recipes on diverse topics
  • Make the most efficient use of your controller and views and reuse them
  • Automate error tracking and understand the Yii log and stack trace
  • Full of practically useful solutions and concepts that you can use in your application, with clearly explained code and all the necessary screenshots

Short story: Rendering a view over ajax in Yii Framework might include JS and CSS files. There is no way for Yii to know that some of these files might already be present in your DOM. Having your browser re-download and add duplicates of these files can or will lead to problems. A short snipped of JS code will filter away all JS and CSS references from incoming content, while leaving inline script and style intact. No server-side changes required. Scroll down for code.

Longer story: If you attempt to render a view with processing of script in Yii Framework you’ll end up with script tags for whichever scripts the components in your view requires. For example, if you have a view which uses the CListView component, you’ll end up with jquery.listview and jquery.js. This is pretty much what you would expect when rendering a view, but it will lead to problems if you are rendering this in an ajax response and the scripts in question are present in the DOM. Yii has no way to know that you have already executed a request, which returned these scripts. There has been some talk on the Yii Framework forums on how to tackle this problem, but so far I’ve not seen any implementations or hacks that’s been satisfactory.

Server side tracking of client scripts
One way to go is to keep track of the script files that has been sent to the client on the server, and reset this state when a full page request is executed. This might seem like a good idea, but once a user starts browsing with several tabs at the same time it gets complicated. It will require hacks to prevent resetting in cases where requests fail midways etc. The state must be kept not only on a path basis, but also for submitted content (ie ajax post), which would further complicate things.

My proposal is to use jQuerys dataFilter hook (via $.ajaxSetup), which is invoked after ajax request and allows you to filter any data returned before it’s passed on to ordinary success-handlers etc.

Update: Thanks to NLAC for some code improvements.

Simply include this script after jQuery, or add this function to your existing code:

$.ajaxSetup({
	global: true,
	dataFilter: function(data,type){
		//  only 'text' and 'html' dataType should be filtered
		if (type && type != "html" && type != "text")
		{
            		return data;
		}
 
		var selector = 'script[src],link[rel="stylesheet"]';
 
      		// get loaded scripts from DOM the first time we execute.
        	if (!$._loadedScripts) {
			$._loadedScripts = {};
			$._dataHolder = $(document.createElement('div'));
 
            		var loadedScripts = $(document).find(selector);
 
			//fetching scripts from the DOM
		        for (var i = 0, len = loadedScripts.length; i < len; i++) 
			{
        		        $._loadedScripts[loadedScripts[i].src] = 1;
            		}
        	}
 
		//$._dataHolder.html(data) does not work
		$._dataHolder[0].innerHTML = data;
 
		// iterate over new scripts and remove if source is already in DOM:
		var incomingScripts = $($._dataHolder).find(selector);
		for (var i = 0, len = incomingScripts.length; i < len; i++)
		{
			if ($._loadedScripts[incomingScripts[i].src])
			{
	        	        $(incomingScripts[i]).remove();
            		}
            		else
            		{
                		$._loadedScripts[incomingScripts[i].src] = 1;
            		}
        	}
 
		return $._dataHolder[0].innerHTML;
	}
});

If you have improvements for this code, please email me at eirikhm@gmail.com