Yii: динамически добавляем, проверяем и удаляем записи

в 1:42, , рубрики: Песочница, метки: ,

Yii: динамически добавляем, проверяем и удаляем записи - 1
Добрый день.
Сегодня очередной чайник представит свой очередной велосипед. Поэтому велосипед получился паровой, таких уже не выпускают, и предназначен для Yii первой версии. Поэтому, для таких, как я, которые плетутся в хвосте истории: делаем форму с динамически добавляемыми моделями, валидируем их, добавляем их в таблички и с чистой совестью удаляем.

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

Из требований — нужна валидация всего добра, которое мы навставляем в форму и это добро не должно отличаться от стандартного функционала.

Выглядеть для пользователя это будет так: поля основной модели, кнопочка «Добавить запись» — добавляет строку полей для новой дочерней модели. В строке должна быть кнопочка «Удалить», которая эту строку удаляет. При валидации сообщения об ошибках будут находится возле тех полей, которые не проходят валидацию на сервере. При сохранении основная и все добавленные модели сохраняются.

Для достижения результата создаю невидимый элемент с полями дочерней модели. Этот элемент буду клонировать для создания в форме новой дочерней записи. Для вывода этого элемента немного меняю созданный мастером шаблон _form.php дочерней модели (даю ему другое имя и добавляю дополнительный параметр, который будет определять индекс дочерней записи, к которой относятся поля).

// было
<div class="row">
	<?php echo $form->labelEx($model,'param'); ?>
	<?php echo $form->textField($model,'param',array('size'=>32,'maxlength'=>32)); ?>
	<?php echo $form->error($model,'param'); ?>
</div>
// стало
<div class="row">
	<?php echo $form->labelEx($model,'['.$index.']param'); ?>
	<?php echo $form->textField($model,'['.$index.']param',array('size'=>32,'maxlength'=>32)); ?>
	<?php echo $form->error($model,'['.$index.']param'); ?>
</div>

То есть у скрытой строки дочерней записи будет индекс 0. Когда будем добавлять элементы формы, то у следующих строк будем менять этот индекс и в результате на сервер будет отправляться массив моделей.

Препарируем стандартные инструменты. Смотрим, что делает jscript в виджете формы: он добавляет для каждого поля формы объект с множеством полей в настройки объекта формы. Правда, отдельным методом, это добавление не оформлено. Но какого велосипедостроителя данный факт пугает? Берем кусок кода и добавляем его в свой скрипт. Бегло посмотрел данный функционал в Yii2 — там все разделено по функциям и, скорее всего, похожий функционал будет реализован меньшей кровью. При нажатии кнопки добавить берем наш невидимый элемент, клонируем его, пробегаемся по всем элементам, у которых есть имя, начинающееся с названия модели и заменяем индекс в имени, id, for на новый очередной индекс. Добавляем в настройки объекта формы данные по новому элементу, чтобы он отправлялся на валидацию, и менял свой облик при ошибках и успешном прохождении проверки, как и родные элементы. Кнопке «Удалить» навешиваем событие по удалению нового клонируемого элемента ( я там удаляю из объекта формы данные по удаляемой строке, но это, наверное, лишнее ). Показываем в форме новую строку. Вроде все.

Далее смотрим серверную часть. Тут нам нужно немного поправить функционал валидации, потому что в существующих вариантах все пляшет от наличия моделей ( то есть берутся модели, сколько есть, и к ним ищутся входные данные ). А в моей идеологии — все наоборот: есть данные и под них нужно найти модель. Для валидации мне хватит одной модели. Поэтому в функцию валидации буду передавать просто массив с названиями моделей ( возможны варианты — передать еще и список аргументов ). Для каждой модели будем просто искать во входных данных соответствующие массивы ( или не массивы, если модель — основная ), присваивать атрибуты из этих данных, проверять и заносить ошибки с нужными индексами в массив ошибок. При ajax валидации отправляем результат обратно в форму. А форма уже готова все ошибки вывести куда надо и раскрасить нужными цветами.

Все это хозяйство оформил в виде расширения. В его составе — виджет для отображения строк дочерних записей и поведение для подключения в контроллере с функциями валидации, добавления и удаления основной и дочерних записей. Чтобы не пугать профессионалов, выложил все на github yiimultirows. Там в комплекте сгенеренное приложение, на котором можно посмотреть, как это работает. В его составе три таблички: основная и 2 дочерних, в форме создания основной записи можно добавлять и удалять записи для дочерних таблиц. Скрипт по созданию табличек в том же архиве.

В принципе можно начинать бросать помидоры. Но пока Вы замахиваетесь, расскажу что не так. Не смог побороть порядок вставки скриптов в страницу, поэтому скрипты виджета вставляются еще до рендеринга формы. В результате этого на момент работы моего jscript еще нет объекта формы и теперь его приходится при добавлении каждый раз искать (можно было бы, наверное, создать внешний объект и при первом запуске его заполнять).

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

Ну и, как обычно, немного рекламы. Смотрите в следующих сериях:

  • делаем сервер для взаимодействия с любыми GPS трекерами (протоколами) на php;
  • превращаем телефон с android в разные полезные штуки (Arduino, конечно, круче, но телефон — уже в корпусе, можно вообще без паяльника обойтись);
  • строим огромный сканер для сканирования всего, что угодно — людей, картин в рамах и т.д (процесс строительства пока не закончен);

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js