Давненько не писал ничего по программированию, а сегодня повод нашёлся. Бывает такая ситуация, не слишком частая, но с виду вполне тривиальная, когда в каком-то представлении нам нужно подгрузить часть контента с помощью аякс (ajax). Всё будет чудесно, если это статический контент, но если подгрузить нужно содержимое, требующее для своей работы подключения javascript или если yii должен генерировать в нём javascirpt, могут начать происходить нетривиальные интересности. Ярчайшим представителем генерации таких интересностей для меня стал виджет формы YiiActiveForm, когда мне потребовалось форму редактирования профиля подгрузить в ajax-табы.
Как вообще отдавать части контента через ajax?
Разумеется, это очень просто. Нужно положить кусочек контента в отдельное представление и использовать метод renderPartial() класса CController:
if(Yii::app()->request->isAjaxRequest)
{
// Прелесть метода renderPartial() в том, что при рендеринге он не выводит layout
$this->renderPartial('_profileForm', array(
'user' => $user,
));
Yii::app()->end();
}
Тут-то и начинается самое интересное: если наше представление требует подключения или генерации (со стороны yii) какого-то javascript кода, js не будет ни подключен, ни выведен. А в моём случае с YiiActiveForm это привело к тому, что перестала работать клиентская javascript и ajax валидация. А в js-консоли виднелась вот такая ошибка:
... yiiactiveform is not a functionПочему после ajax загрузки с помощью renderPartial() не добавляется JavaScript?
На самом деле renderPartial() был создан не для работы с ajax. Читатель этого поста наверняка замечал, что Yii по умолчанию добавляет подключаемые js библиотеки в начало html кода (тег head), а js, необходимый для работы конкретных элементов генерирует в конце страницы (перед закрытием body). Так вот, при использовании renderPartial() yii и не должен добавлять в него ни строчки js.
А этот renderPartial() не так-то прост
Оказывается, мы можем передать этому методу ещё третий и четвёртый boolean-аргументы: $return, а так же $processOutput, которые по умолчанию принимают значение false. При установленном $return = true, renderPartial() включит буферизацию вывода, что удобно, если необходимо не выводить представление в браузер, а, к примеру, присвоить его какой-то переменной или добавить к какой-то строке. Нас же ещё больше интересует четвёртый параметр, при установке которого $processOutput = true будет произведена постобработка нашего представления, в результате которой в него будут добавлены все скрипты, которых оно требует:
if(Yii::app()->request->isAjaxRequest)
{
// Так выглядит renderPartial() с передачей параметра $processOutput = true
$this->renderPartial('_profileForm', array(
'user' => $user,
), false, true);
Yii::app()->end();
}
Печально, но приключения на этом не заканчиваются. После загрузки всё той же YiiActiveForm таким способом форма-то заработала, но перестал работать весь остальной javascript, к примеру, возникли проблемы при попытке инициализировать js-плагины для только что загруженной формы.
Почему после ajax загрузки с помощью renderPartial() + $processOutput перестаёт работать JavaScript?
Заглянув в js-консоль, мы увидим, что после того, как новый контент добавился в DOM, происходит повторная загрузка и подключение jquery.js, jquery.ui.js, jquery.yiiactiveform.js и других библиотек, которые требуются для работы нашему представлению. Соответственно, это вызывает сбои в работе уже подключенных библиотек и прочие затупы. Кстати, это совершенно соответствует описанному в документации поведению метода, ведь renderPartial() не на столько крут, чтобы знать, что у нас уже подключено, а что - нет.
Побороть повторную загрузку скриптов можно при помощи карты скриптов:
if (Yii::app()->request->isAjaxRequest)
{
Yii::app()->clientScript->scriptMap = array(
// В карте отключаем загрузку core-скриптов, УЖЕ подключенных до ajax загрузки
'jquery.js' => false,
'jquery.ui.js' => false,
// На моей странице была ещё одна форма, поэтому jquery.yiiactiveform.js я исключил
'jquery.yiiactiveform.js' => false,
);
}
На этом всё. После ajax загрузки в наше представление добавится код подключение только тех скриптов, которые не были исключены в карте. Да здравствует торжество научной мысли!