yiisoft / jquery-pjax

pushState + ajax = pjax
http://pjax.herokuapp.com
MIT License
144 stars 40 forks source link

Инициализация ActiveForm после PJAX #39

Closed AnikinViktor closed 8 years ago

AnikinViktor commented 8 years ago

Добрый день! В блоке Pjax имеется ListView и ActiveForm. Если валидатор на стороне сервера нашел ошибку, то клиенту возвращается форма с ее содержимым и соответствующие сообщения об ошибках. Когда пользователь начинает повторно вводить в данные форму, то оказывается, что валидаторы на стороне клиента не отрабатывают, т.е. вообще не реагируют на ввод данных, хотя соответствующий JS код передан. Я могу ошибаться, но мне кажется проблема в следующем. При первой загрузке страницы к форме прикрепляется атрибут yiiActiveForm - его наличие свидетельствует о том, что форма уже ранее была инициализирована и не требует повторной инициализации. if ($form.data('yiiActiveForm')) { return; } Когда форма повторно возвращается через Pjax и помещается в DOM документа, отрабатывает JS код инициализации формы, который пришел вместе с ее HTML кодом. Таким образом повторно отрабатывает init в yii.activeForm.js: запрашивается атрибут yiiActiveForm формы и jQuery успешно возвращает его и насколько я понял, возвращает его из кэша: var cache = this.cache[ this.key( owner ) ]; return key === undefined ? cache : cache[ key ]; Мне удалось обойти эту проблему добавлением <?php $this->registerJs('jQuery("#postDeliveryInfoForm").removeData("yiiActiveForm");'); ?> в представлении формы сразу после ActiveForm::begin(). Получается после загрузки формы удаляется информация об ее инициализации (если конечно она есть) и форма успешно инициализируется, т.е. начинают работать валидаторы на стороне клиента при изменении содержимого полей.

В связи с этим вопрос: может стоит в методе инициализации формы поместить код if ($form.data('yiiActiveForm')) { $form.removeData('yiiActiveForm'); } чтобы форма повторно инициализировалась?

SilverFire commented 8 years ago

имеется ListView и ActiveForm

ListView, насколько я понимаю, никак влияет на поведение Pjax?

Ниже привожу diff изменений в yii2-app-basic, которые я внес для воспроизведения проблемы:

SiteController:

diff --git a/controllers/SiteController.php b/controllers/SiteController.php
index 4ffba24..742b92c 100644
--- a/controllers/SiteController.php
+++ b/controllers/SiteController.php
@@ -77,11 +77,17 @@ class SiteController extends Controller
     public function actionContact()
     {
         $model = new ContactForm();
-        if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) {
-            Yii::$app->session->setFlash('contactFormSubmitted');
+        if ($model->load(Yii::$app->request->post())) {
+            if ($model->validate()) {
+                $model->contact(Yii::$app->params['adminEmail']);
+                Yii::$app->session->setFlash('contactFormSubmitted');
+            }

-            return $this->refresh();
+            return $this->renderAjax('contact', [
+                'model' => $model,
+            ]);
         }
+
         return $this->render('contact', [
             'model' => $model,
         ]);

В модели ContactForm я выключил клиентскую валидацию для полей 'name', 'subject', 'body', чтобы только отправка формы и серверная валидация могли показать ошибку

diff --git a/models/ContactForm.php b/models/ContactForm.php
index 361b80b..12267f9 100644
--- a/models/ContactForm.php
+++ b/models/ContactForm.php
@@ -23,11 +23,10 @@ class ContactForm extends Model
     {
         return [
             // name, email, subject and body are required
-            [['name', 'email', 'subject', 'body'], 'required'],
+            [['name', 'email', 'subject', 'body'], 'required', 'enableClientValidation' => false],
+            ['email', 'trim'],
             // email has to be a valid email address
             ['email', 'email'],
-            // verifyCode needs to be entered correctly
-            ['verifyCode', 'captcha'],
         ];
     }

@@ -49,13 +48,7 @@ class ContactForm extends Model
     public function contact($email)
     {
         if ($this->validate()) {
-            Yii::$app->mailer->compose()
-                ->setTo($email)
-                ->setFrom([$this->email => $this->name])
-                ->setSubject($this->subject)
-                ->setTextBody($this->body)
-                ->send();
-
+            Yii::info('The message was sent.');
             return true;
         }
         return false;

В представление добавил Pjax и ListView:

diff --git a/views/site/contact.php b/views/site/contact.php
index b988409..348daa3 100644
--- a/views/site/contact.php
+++ b/views/site/contact.php
@@ -7,6 +7,7 @@
 use yii\helpers\Html;
 use yii\bootstrap\ActiveForm;
 use yii\captcha\Captcha;
+use yii\widgets\Pjax;

 $this->title = 'Contact';
 $this->params['breadcrumbs'][] = $this->title;
@@ -33,6 +34,20 @@ $this->params['breadcrumbs'][] = $this->title;

     <?php else: ?>

+        <?php Pjax::begin() ?>
+
+        <p>Sample list view</p>
+
+        <?php echo \yii\widgets\ListView::widget([
+            'dataProvider' => new \yii\data\ArrayDataProvider([
+                'allModels' => [
+                    ['id' => 1, 'name' => 'SilverFire'],
+                    ['id' => 2, 'name' => 'AnikinViktor'],
+                ]
+            ])
+        ]) ?>
+
+
         <p>
             If you have business inquiries or other questions, please fill out the following form to contact us.
             Thank you.
@@ -40,8 +55,10 @@ $this->params['breadcrumbs'][] = $this->title;

         <div class="row">
             <div class="col-lg-5">
-
-                <?php $form = ActiveForm::begin(['id' => 'contact-form']); ?>
+                <?php $form = ActiveForm::begin([
+                    'id' => 'contact-form',
+                    'options' => ['enctype' => 'multipart/form-data', 'data-pjax' => true],
+                ]); ?>

                     <?= $form->field($model, 'name')->textInput(['autofocus' => true]) ?>

@@ -64,5 +81,6 @@ $this->params['breadcrumbs'][] = $this->title;
             </div>
         </div>

+        <?php Pjax::end() ?>
     <?php endif; ?>
 </div>

Заполняю форму, оставляю одно из обязательных полей незаполненным. На клиенте ошибки нет, так как валидацию я отключил. Нажимаю submit, форма получает ответ от сервера и подсвечивает незаполненное поле красным. Поля, для которых клиентская валидация включена нормально реагируют.

Проблему воспроизвести не смог.

AnikinViktor commented 8 years ago

Зачем Вы отключаете валидацию на клиенте??? В своем сообщении я указывал о том, что после перезагрузки блока PJAX, в который включена форма, перестают отрабатывать валидаторы на стороне клиента. Включите валидацию на стороне клиента, затем пусть пользователь корректно заполнит модель, отправит ее на сервер, а сервер в свою очередь вернет ее с какой-нибудь ошибкой. Когда форма будет возвращена клиенту - попробуйте оставить пустыми поля, отмеченные как required и увидите, что валидаторы на стороне клиента больше не отрабатывают. Вы можете оставить все поля пустыми - валидатор их не подсветит красным цветом.

AnikinViktor commented 8 years ago

Проблема вроде разрешилась обновлением jQuery. Буду тестировать.

SilverFire commented 8 years ago

Зачем Вы отключаете валидацию на клиенте???

Если у вас включена валидация на клиенте, вы не сможете отправить форму, пока не исправите ошибки. Я мог бы создать отдельное правило валидации в модели, чтобы оно всегда порождало ошибку, но предпочел просто отключить валидацию некоторых правил на клиенте. Все равно я не смог воспроизвести вашу проблему

Проблема вроде разрешилась обновлением jQuery. Буду тестировать.

Если все таки не разрешилась - дайте знать