Yii Active Form и другой Javascript контент, загруженный через ajax в Yii Framework 1.x

Давненько не писал ничего по программированию, а сегодня повод нашёлся. Бывает такая ситуация, не слишком частая, но с виду вполне тривиальная, когда в каком-то представлении нам нужно подгрузить часть контента с помощью аякс (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 загрузки в наше представление добавится код подключение только тех скриптов, которые не были исключены в карте. Да здравствует торжество научной мысли!