Ajax подгрузка контента и JavaScript

Предварительное обсуждение найденных ошибок перед отправкой их авторам фреймворка, а также внесение новых предложений.
Ответить
von.hamster
Сообщения: 69
Зарегистрирован: 2013.06.06, 16:07

Ajax подгрузка контента и JavaScript

Сообщение von.hamster »

Опишу проблему и текущее решение. Может кто подскажет более красивый или правильный вариант.

Проблема:
Есть страничка, на которую подгружается форма в указанный div.
В контроллере стандартный (почти) код, только контент рендерится частично:

Код: Выделить всё

    public function actionCreate()
    {
        $model = new Client();

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
	    return $this->renderPartial('ok');
        }

        return $this->renderPartial('_form', [
            'model' => $model,
        ]);
    }
Проблема в том, что на форме есть скрипты (или виджеты со скриптами). Соответственно - в таком варианте они работать не будут (если скрипт зарегистрирован через $this->registerJs() или подключается ассет).

Если изменить на renderAjax - появляется другая проблема: если виджет имеет свой ассет, и еще с зависимостями - то в подгружаемый контент попадают все эти скрипты, которые, в том числе, дублируют уже загруженные на основной странице. И это часто вызывает проблемы.

Основной вопрос - как подгружать контент, в котором бы подгружались только те ассеты, которые еще не загружены, и выполнялись скрипты.

Варианты, которые рассматривал:
1. отказаться от стандартных динамических виджетов, и инициализировать нужные поля после загрузки ajax (вариант - в шаблоне формы прописать инициализацию вручную, без использования registerJs). Минусы - очень неудобно на больших формах с разнообразием полей, и если виджеты внутри себя динамически генерят параметры или код.
2. подгружать в iframe - частично решает проблему, но есть свои особенности и неудобства.

Мое временное решение следующее:
Покопался в коде фреймворка и нашел метод View->renderBodyEndHtml, который, делает почти то, что мне нужно. Но есть 2 НО:
1. он protected
2. он не вызывает скрипты, добавленные через registerJs, которые с POS_HEAD (для этого есть renderBodyBeginHtml). Некоторые виджеты так инициализируют используемую библиотеку или какие-то параметры.

В итоге, на основе метода renderBodyEndHtml сделал хэлпер:

Код: Выделить всё

    public static function renderJs(View $view): string
    {
        $lines = [];

        // Тут, по идее, должны были бы подгружаться данные из ассетов (но это не точно), но их нет, что меня устраивает.
        if (!empty($view->jsFiles[View::POS_END])) {
            $lines[] = implode("\n", $view->jsFiles[View::POS_END]);
        }
        if (!empty($view->js[View::POS_HEAD])) {
            $lines[] = Html::script(implode("\n", $view->js[View::POS_HEAD]));
        }
        if (!empty($view->js[View::POS_END])) {
            $lines[] = Html::script(implode("\n", $view->js[View::POS_END]));
        }
        if (!empty($view->js[View::POS_READY])) {
            $js = "jQuery(function ($) {\n" . implode("\n", $view->js[View::POS_READY]) . "\n});";
            $lines[] = Html::script($js);
        }
        if (!empty($view->js[View::POS_LOAD])) {
            $js = "jQuery(window).on('load', function () {\n" . implode("\n", $view->js[View::POS_LOAD]) . "\n});";
            $lines[] = Html::script($js);
        }

        return empty($lines) ? '' : implode("\n", $lines);
    }
Оставляю renderPartial и вызываю этот метод в конце шаблона формы. А необходимые ассеты вручную добавляю в зависимости основного ассета или подгружаю в шаблоне основной страницы.

Для моих целей, в принципе, проблема решена (тоесть пока все работает).
Но я считаю, что это, не оптимальное и не очень красивое решение.
Ответить