Баг: повторный вызов javascript-файлов

Столкнулся с таким неприятным багом:

Если происходит рендеринг вьювера с параметром $processOutput = true, то заново грузятся все скрипты.

Само собой при повторной инициализации jquery, ранее повешенные события перестают работать.

Подобная проблема и ее фикс описан в этом топике.

Если кратко, то саму библиотеку jquery подключаем стандартным способом, и прописываем в лайауте


Yii::app()->clientScript->scriptMap['jquery.js'] = false;

Но это не решает проблемы повторной загрузки скриптов. Кто то сталкивался с подобным, или кто знает решение?

P.S. я немного покопал класс CClientScript, и обнаружил что при $processOutput = true все скрипты в конечном итоге загружаются через CHtml::scriptFile(), без проверки на их наличие.

я это решил использованием nyroModal плагина (модальное окно делает) так вот этот вполне себе хороший плагин не загружает без специального указания внешние срипты :)

А в целом после отказался от генерации яваскрипта средствами CHtml

Нет, вы наверно не совсем поняли суть проблемы.

Приведу пример:

На странице есть виджет ActiveForm, captha + несколько своих скриптов, которые привязаны к определенным событиям.

Виджеты содержат скрипты и события, которые должны быть инициированы при загрузке страницы.

при обычном заходе на страницу, все работает, без каких бы то ни было проблем (в контроллере используется $this->render())

при ajax-запросе (в моем случае - это загрузка контента в модальное окно), скрипты грузятся повторно, в контроллере вызывается метод $this->renderPartial(<view>, <data>, false, true) для того чтобы подгрзузить не весь контент, а только нужный вьювер, 4-ый параметр в true, чтобы были загружены скрипты используемые в виджетах (более глобально, те, которые подключаются через registerScriptFile() )

Странно еще то, что при вызове $this->render() скрипты грузятся из директории, указанной в Yii::app()->getClientScript()->setCoreScriptUrl(’/js’),

а при $this->renderPartial() - по умолчанию из /assets/…

Пока я вижу 2 решения проблемы:

  1. переписать системынй класс CClientScript

  2. не использовать во вьюверах, которые должны подгружаться асинхронно, методы registerScriptFile() registerScript(). Вместо этого писать <script type="text/javascript">$(document).ready(function () { …. А чтобы иницировать события в виджетах, то надо также повторно прописывать еще и для них события.

Почему 1) и 2) плохо думаю объяснять не стоит.

Есть идеи??

Вы вероятно не поняли мой ответ.

Я прекрасно понимаю то о чем Вы написали и сталкнулся с этим достаточно давно.

Поэтому и сказал Вам что в качестве модального окна использу. nyromdal ( в гугле найдете )

так вот ниромодал игнорирует внешние скрипты. Когда Юии сгенерирует контент для аякс при включенно processOutput’е он вставит и зависимые от всех этих штучек - jquery.js и иже с ним …

Так вот Ниромодал если не указать никак доп. параметр в тэг script не будет загружать их и проблема решается.

Так что Вашу проблему я понимаю и рассказал как Вам ее без костылей

и кстати очень элегантно можно решить.

А кстати грузятся все скрипты у Вас потому как капча наверняка исп. механизы Yii для перегенерации не перегружая страницу. вот и нужен ей для этого jquery

Я понял о чем вы говорите, и плагин смотрел. Но он, решая одну проблему, приводит к другой. собственно то что было, только наоборот.

Получается, что для каждой такой "ссылки" необходимо указывать какие скрипты надо иницировать в окне. Шило на мыло одним словом. Более того, надо вручную генерировать код, который создают виджеты, чтобы он тоже запустился. Это совсем уже не то.


Для простоты понимания проблемы: есть страница с формой, валидируемая ajax-ом посредствам ActiveForms. + на страничке UI-datepicker + yii-captha. При загруке такой странички посредсвтам ajax, мы получаем описаные проблемы.

Пока что ни одно решение не является красивым и оптимальным. :(

[size="1"](прошу не говорить о том, что лучше использовать другие валидаторы, разговор не о том)[/size]

Вы бы потратили чуть больше времени на его изучение, может быть смогли бы дать ответы на свои вопросы.

http://nyromodal.nyrodev.com/wiki/index.php/Deal_with_JavaScript_in_an_Ajax_loaded_page

А вот тут по подробнее … что Вы имеете в виду в ручную ?

Вот именно по этому nyroModal мне нравится.

Загрузил через аякс какую нить форму и дал ей ТОЛЬКО класс nyroModal. и уже сабмит в этом окне пройждет тоже аксом.

И файл отправит и вернет результат тоже в окно. Примитивно просто и бытсро. и отчасти даже краиво.

Указанный вики читал ранее, он решает проблему, если на стрнице есть свои скрипты. Повторюсь, проблема в скриптах, которые ГЕНЕРИРУЕТ сам виджет.

Подробнее:

есть вьювер smth.php


<?php $form=$this->beginWidget('CActiveForm', array(

	'id'=>'ajax_form',

	'enableAjaxValidation'=>true,

	'clientOptions' => array(

		'validateOnSubmit' => true,

		'validateOnChange' => false

	)

	

)); ?>

	<div class="row">

		<?php echo $form->labelEx($model,'ip'); ?>

		<?php echo $form->textField($model,'ip'); ?>

		<?php echo $form->error($model,'ip'); ?>

	</div>



При открытии странички с данным вьювером) виджет подключает необходимые скрипты (в частности, jquery.yiiactiveform.js) и с помощью registerScriptFile() вставляет в конец страницы примерно такой код [size="1"](чтобы добавить атрибуты для подключаемого скрипта, мне необходимо править системные виджеты)[/size]


$('#ajax_form').yiiactiveform({'validateOnSubmit':true,'validateOnChange':false,'attributes':....})

Следовательно, чтобы воспользоваться всеми прелестями предлагаемого плагина жКвери, необходимо хиррургическое вмешательство в фреймворк.

Если что то непонятно, то пишите лучше в личку.

К слову, остановился на решении, предложенном в подобном топике. Для частного случая вполне приемлемо, и всего 1 строчка кода…

и на каком решении вы остановились ??

я несколько месяцев назад занес в багрепорт предложение по улучшению, недавно мне сообщили что в ближайщем релизе появится такая возможность.

Но в том проете я решил эту проблему просто подключением требуемого срикпта через include

в index.php внес


Yii::app()->clientScript->scriptMap['jquery.js'] = false;

Yii::app()->clientScript->setCoreScriptUrl('/js'); 



чтобы игнорировать загрузку библиотеки jquery. В лайауте размещать нельзя, т.к. при использовании renderPartial() будут взяты дефолтные настройки.

Папку для скриптов тоже изменил, т.к. был модифицирован скрипт ActiveForm - добавил возможность редиректа и выполнения своей функции после успешной валидации и сабмита формы. Если кому надо, могу выложить файл.

Будем ждать. К сожалению предложенный вами способ не всегда получится применить, тем не менее спасибо за советы :)

ух, ребята, оказывается не только у меня такие проблемы возникают )) я (дабы не клювать себе мозг и забыться на эту проблему) решил сделать так (привожу сам принцип - кусок выдран с рабочего проекта))

прежде всего кусочек js:




var md={

	/**

	 * All settings recursive initialization

	 * (adds parentScope() method to each scope)

	 * 

	 * @param object $

	 * @param object scope

	 * @return void

	 */

	init: function($, scope){

		var bind=function(fn,context){

			return function(){

				var ret=fn.apply(context,arguments);

				return (undefined===ret)?context:ret;

			};

		}, fnScope=function(){

			return scope;

		}, callee=arguments.callee;


		$.each(scope, function(name, context){

			// delegation & recursion

			if('object'===typeof(context) && context && !$.isArray(context))

				callee($,$.extend(context,{parentScope:fnScope}));

			else if($.isFunction(context))

				scope[name]=bind(context,scope);

			if($.isFunction(context.init))

				context.init();

		});

	},

	/**

	 * Additional to application.clientScript

	 * @var object

	 */

	clientScript: {

		/**

		 * Default collection of already included scripts/css

		 * @var array

		 */

		scripts: ['jquery.js'],

		/**

		 * ClientScript initialization

		 */

		init: function(){

			this.initMap().fixAjax().fixAjaxSetup();

		},

		/**

		 * Collect already loaded scripts

		 */

		initMap:function(){

			this.scriptMap.apply(this,$('script[src!=],link[rel=stylesheet][href!=]').map(function(i,item){

				return (item=$(item)).attr(item.is('script')?'src':'href').replace(/.*?\/([^\/]+)$/,'$1');

			}).toArray());

		},

		/**

		 * @param string argument

		 * @return null|array

		 */

		scriptMap:function(){

			var scope=this,

				args=arguments;


			if(args.length>0)

				scope.scripts=scope.scripts.concat($.map(args,function(script){

					if(-1===$.inArray(script,scope.scripts))

						return script;

				}));

			else

				return scope.scripts||[];

		},

		fixAjax: function(){

			var ajax=$.ajax,

				scriptMap=this.scriptMap;


			$.ajax=function(options){

				if(!options.data)

					options.data='scriptMap='+scriptMap().join(",");

				else if(-1===options.data.indexOf('scriptMap'))

					options.data+='&scriptMap='+scriptMap().join(",");


				return ajax.apply(this,arguments);

			};

		},

		/**

		 * Add complete function to AjaxSetup to extract used scripts/css for scriptMap object

		 * @return void

		 */

		fixAjaxSetup: function(){

			var scriptMap=this.scriptMap;

			$.ajaxSetup({

				success:function(data){

					if(data)

						$.map(data.match(/<(script|link)[^<>]*?(src|href)[^<>]*?>/g)||[],function(tag){

							scriptMap(tag.match(/(src|href)=(["']).*?\/([^\/]+)\2/)[3]);

						});

				},

				complete:function(xhr,text){

					if(xhr.responseText)

						$.map(xhr.responseText.match(/<(script|link)[^<>]*?(src|href)[^<>]*?>/g)||[],function(tag){

							scriptMap(tag.match(/(src|href)=(["']).*?\/([^\/]+)\2/)[3]);

						});

				}

			});

		}

	}

};




$(function(){

	md.init($, md);

});



суть его - коллекционировать то, что уже есть на странице или то, что было динамически подгружено… вызывается init() на ready() документа, все остальное, как вы могли догадаться, дело техники (добавляем следующий кусочек на beforeAction в своем контроллере):




/* @var $cs CClientScript */

$cs=Yii::app()->getClientScript();

if(null!==($scriptMap=Yii::app()->request->getParam('scriptMap')) && is_string($scriptMap)){

	foreach(explode(',', $scriptMap) as $script)

		$cs->scriptMap[$script]=false;

}



результат - все счастливы :rolleyes: удачи! (будут вопросы - спрашивайте)

Вообще не понял что такое в последнем сообщении =) что куда вставлять и что за здоровенный кусок кода ))) нельзя ли поподробнее??

суть - коллекционирование js/css (+дальнейшее коллекционирование при появлении нового содержимого), которые уже есть на клиенте, в каждом ajax запросе - передача этой коллекции на сервер, где тот в beforeAction пробегаемся по коллекции scriptMap, которые берутся из запроса и говорит что "ИХ ВСТАВЛЯТЬ В КОНЕЧНЫЙ XHTML УЖЕ НЕ НУЖНО".

Кусок js - в любое место (лишь бы подключался после jQuery),

Кусок php - я описал… добавляем в beforeAction

Приветствую, господа!

Подскажите, на данный момент имеется "правильное решение" обсуждаемой в данной теме проблемы?

Я пока решаю это костылем, прописывая в форме, подгружаемой в модальном диалоговом окне, к id - time(). Это позволяет submit-кнопку делать уникальной при каждой загрузке формы (без обновления страницы). Если этого не делать то при повторном открытии диалога submit-запрос отрабатывается столько раз, сколько открывалась форма.


echo CHtml::ajaxSubmitButton ('Добавить >>',

	CHtml::normalizeUrl(array('postTag/addPostTagDialog')),

	array ('update'=>'#posttagzone',

		'type'=>'POST'),

	array ('type'=>'submit',

		'onClick'=>'$("#posttagdialog").dialog("close"); return false;',

		'id'=>'addPostTagSubmit'.time(),

		'name'=>'addPostTagSubmit')

);