Обед задержан на 3 часа, выпита сверхнорма чая...
Всё из-за дерзких кнопок "Добавить ещё", упорно не желающих передавать в $form_state['values'] значения всех остальных полей формы, кроме своих собственных.
Гениальные Form API планы рушатся, энтузиазм падает в сторону CSS и JQuery.
Без особой надежды пишу эти строки вам, закалённые Друпалурги, имеющие лишние килобайты внимания на добрый совет...
Ну а теперь без лирики и по существу.
Хотелось бы доброго совета.
Рассказ долгий, но подробный. Тому кто с динамическим API форм не знаком - в копилку.
Как всё начиналось и чего хотелось
Я хотел сделать два зависимых поля (допустим POLE-1 и POLE-2 - ссылки на термины таксономии - два разных словаря)
POLE-1 - термины словаря-1
POLE-2 - термины словаря-2, которые ссылаются на термины словаря-1 через поле ссылки на термин.
Выбираешь в POLE-1 термин "СУПЕРТЕРМИН" словаря-1
и список терминов в POLE-2 ограничивается выборкой тех что ссылаются на "СУПЕРТЕРМИН"
И всё это при помощи динамического Form API
У меня есть форма создания/редактирования ноды, которую я ковыряю по букварю функцией MY_MODULE_form_node_form_alter(&$form, &$form_state)
- прописал #ajax в свойстве элемента POLE-1,
где указал, какая callback функция моего модуля будет возвращать нужный рендер массив или html
и целевым контейнером обернул POLE-2 которое будет заменёно силами JQuery в отображаемой форме,
ну и тип события разумеется - change
- POLE-2 - тоже не большая проблема
выбрать нужные термины,
сделать массив опций,
припихнуть их в #options элемента POLE-2
и вернуть этот элемент из callback функции
Проблема подкралась в кеше форм
Судя по этому комменту с друпалорга и букварю Томлинсона и Вандюка при создании и использовании динамических полей в форме порядок работы такой:
- Юзер получает доступ к форме
- Строится PHP массив формы
- Сохраняется именно тот набор полей и опций который будет отправлен в броузер и прикрепляется уникальный ключ
- Юзер изменяет POLE-1 провоцируя Ajax сабмит (внимание) ВСЕХ данных формы
- Друпал сравнивает по уникальному ключу пришедший набор полей и опций с кешем, который сохранил в пункте 3 и (ЗДЕСЬ ГВОЗДЬ) генерит ошибку если набор отличается, чтобы хакеры не подпихивали свои поля к форме.
- строит форму по новой и даёт изменить её моему модулю.
- сохраняет кэш формы снова по пункту 3
- передаёт форму в callback функцию чтобы та только вернула определённый элемент, который нужно заменить в форме. Иначе, если изменить форму в callback функции, сравнение с кешем в пункте 5 не пройдёт.
- Юзер сабмитит форму и она опять сравнивается с кешем, который создавался при AJAX запросе.
И всё бы хорошо. Мой модуль честно возвращает новый элемент на основе полученных из &$form_state['values'] данных о введённом значении POLE-1. Ведь моё динамическое POLE-1 передаёт ВСЕ поля в &$form_state['values']...
А кнопка Add More у друпаловских полей с множественными значениями нет.
Она отправляет при своём Ajax сабмите в &$form_state['values'] только данные о своём элементе, как будто других полей вообще нет.
Получается такая ситуация в моей динамической форме ноды:
- я выбираю POLE-1
- происходит сабмит со ВСЕМИ полями формы
- массив формы строится по новой
- я подцепляю его в своём модуле через MY_MODULE_form_node_form_alter
- смотрю &$form_state['values']
- генерю набор опций в POLE-2
- набор и вся форма в принципе сохраняется в кеш форм
- отправляется в callback и оттуда нужный элемент в броузер
- выбираю какой нибудь термин в POLE-2 горяченьком, только из ajax-a
- потом я жму "Добавить ещё" на любом другом множественном поле которое есть в форме
- происходит новый AJAX сабмит, но данных о введённом поле POLE-1 $form_state['values'] НЕТ! я не могу сгенерить аналогичный набор опций в своей функции MY_MODULE_form_node_form_alter
- строится форма вообще без набора опций POLE-2 (хотя в браузере висит набор опций с одним из терминов отмеченным)
- форма без набора опций POLE-2, не соответствующая той, которая висит и будет отправлена из браузера - кешируется
- я в браузере жму "Сохранить"
- Ошибка - недопустимый выбор. Ведь Кеш не соответствует отправленному из браузера.
Вот собственно и вся песня.
Измучился, никак не придумаю что делать.
Теперь думаю пусть уж POLE-2 выводит весь гамуз опций-терминов а я их отфильтрую и скрою к чертям JavaScript-ом и CSS исходя из значения в POLE-1, но это не DRUPAL-way выходит.
Буду рад совету ибо энтузиазм заканчивается вместе с этим постом...
Версия Drupal 7.26
Всем добра.
Комментарии
Самый крутой модуль Максима Подорова http://drupal.org/project/ddf, решает вашу задачу мышкой.
«Самый крутой модуль Максима Подорова http://drupal.org/project/ddf, решает вашу задачу мышкой.»
Благодарю, Александр!
Сам же сто раз читал, что на всё есть модуль. Ох уж эта тяга ковыряться самому...
Завтра опробую.
Попробуйте, но возможно это не совсем то, что вам нужно, но модуль может делать зависимости двух entityreference филдов, с передачей параметров в другой ... мне честно говоря неохота было до конца вникать в вашу проблему
... а что касается ковыряний, так в этом нет ничего плохого, судя по тому что я смог уловить, вы довольно плохо знаете FORM API и как он работает, а эта задача хороший повод исправить такое положение.
А код глянуть можно формы и сабмита?
Александр, да мне до нормального знания API ещё далеко. Вот ковыряюсь по чуть...
Модуль сейчас опробую...
Dimychka, вот примерная схема того что я делал:
<?php
function MY_MODULE_form_node_form_alter (&$form, &$form_state, &$form_id)
{
//Здесь код который по значению $form_state['values']['POLE_1'][LANGUAGE_NONE][0]["tid"]
// достаёт из базы список терминов - $optionsArray, чтобы вставить его в POLE_2
//когда из броузера происходит ajax сабмит моим POLE_1 в $form_state['values'] есть все поля формы ноды
//а когда происходит сабмит стандартной кнопкой "Add More" какого-нибудь другого поля с множественным значением,
//в $form_state['values'] нет ничего кроме массивов с данными по этому множественному полю
//и не только при сабмите Add More сторонего поля, походу...
//заметил, что при работе с полем типа image та же беда...
//когда выбираешь фото и подгружаешь его в форме ноды (появляется превьюшка фото) - опять сабмит с не полным $form_state['values']
//вставляем в элемент POLE_2 полученный из базы массив оций
$form["POLE_2"][LANGUAGE_NONE]["#options"] = $optionsArray;
//прикрепляю #ajax к POLE_1...
$form["POLE_1"][LANGUAGE_NONE]["#ajax"] = array(
'event' => 'change',
'callback' => '_return_pole_2',
'wrapper' => 'wrapper_id'
);
//оборачиваю в POLE_2 в div
$form["POLE_2"][LANGUAGE_NONE]["#prefix"] = '<div id="wrapper_id">';
$form["POLE_2"][LANGUAGE_NONE]["#suffix"] = '</div>';
}
//форма сохраняется в кеше
//и передаётся в указанную в #ajax свойстве функцию _return_pole_2
function _return_pole_2(&$form, &$form_state)
{
//здесь просто отправляем в броузер рендер массив POLE_2
return $form["POLE_2"];
}
?>
Происходит следующее:
1 - выбираю в форме POLE_1
происходит сабмит с полным набором данных в $form_state['values'].
Строится массив опций для POLE_2
2 - кешируется форма с набором опций POLE_2
и POLE_2 отправляется на обновление в браузер
3 - выбираю какую-нибудь опцию в POLE_2
4 - провоцирую кнопкой Add More совсем другого поля новый сабмит
5 - сабмит проходит так как кеш из шага 2 совпадает с тем что я выбрал на шаге 3
6 - но $form_state['values'] не полное при сабмите с Add More и форма строится вообще без набора для POLE_2
но при этом в браузере он висит и одна опция отмечена
7 - текущий кеш формы сохраняется без опций POLE_2, а в броузере они есть и одна из них отмечена к отправке
8 - поле спровоцировавшее сабмит кнопкой Add More обновляется в броузере
9 - любой последующий сабмит вызовет ошибку,
так как на валидацию из броузера придёт значение POLE_2 а в текущем кеше из шага 7 его нет.
А можно полный код с формой, по этому куску непонятно, на первый взгляд все верно
Dimychka, имеешь ввиду рендер массив формы ноды?
Она просто большушая c кучей полей и field_collection...
Просто когда появилась ошибка "Недопустимый выбор" я 100500 раз перепроверял почему
и обнаружил, что фишка именно в том что при ajax сабмите другими полями,
(которые как-то обновляются в браузере при заполнении ноды)
в $form_state['values'] не полный набор полей из формы...
при сабмите моим полем к которому я подцепил #ajax, в $form_state['values'] есть все поля.
Когда наткнулся на это, написал данный пост.
Вот могу привести рендер массив конкретно той кнопки которая даёт мне сбой
У меня есть множественное поле field_eshe_tovari - это entity reference на другие ноды
в $form['field_eshe_tovari']['und']['add_more'] - рендер массив для стандартной друпаловской кнопки "Добавить ещё"
нажимая на которую добавляешь ещё одно поле ссылки (автокомплит) на другую ноду.
С этой кнопки (и других add more) происходит ajax сабмит с неполным $form_state['values']
<?php
['add_more'] => Array
(
['#type'] => 'submit'
['#name'] => 'field_eshe_tovari_add_more'
['#value'] => 'Добавить ещё'
['#attributes'] => Array
(
['class'] => Array
(
[0] => 'field-add-more-submit'
)
)
['#limit_validation_errors'] => Array
(
[0] => Array
(
[0] => 'field_eshe_tovari'
[1] => 'und'
)
)
['#submit'] => Array
(
[0] => 'field_add_more_submit'
)
['#ajax'] => Array
(
['callback'] => 'field_add_more_js'
['wrapper'] => 'field-eshe-tovari-add-more-wrapper'
['effect'] => 'fade'
)
)
?>
При нажатии на эту кнопку ("добавить ещё" для поля field_eshe_tovari)
в $form_state['values'] есть только массивы field_eshe_tovari и field_eshe_tovari_add_more
Сейчас копаю в сторону того что может быть такого особенного с сабмитом при нажатии на неё...
Может в функции field_add_more_submit как то обрезается $form_state... сижу разбираюсь
Кажется я начинаю понимать в чём косяк...
field_add_more_submit в конце ставит $form_state['rebuild'] = TRUE;
форма перестраивается с дефолтными значениями и пустым $form_state...
Но проблема наверно в подходе к изменению формы...
Сейчас опробую и отпишусь...
Без результата пока что, но двигаюсь в направлении таком:
Насколько я понял
в функции MY_MODULE_form_node_form_alter можно оставлять максимально большой набор опций POLE_2 чтобы он попал в кэш,
а в ajax callback функции, которая должна возвращать в браузер элемент, можно этот набор сужать, но не расширять,
иначе придётся с помощью $form_state['rebuild'] = TRUE перестраивать форму (как это сделать грамотно, пока не знаю). Перестраивать надо чтобы добавленная опция, будучи отмеченной в броузере, оказалась в кеше и не вызывала ошибку недопустимого выбора.
В общем завтра буду эту схему прорабатывать - то есть делать выборку опций POLE_2 в приемущественно в ajax callback функции (а в MY_MODULE_form_node_form_alter только при первой загрузке формы).
К сожалению с модулем Dynamic dependent fields не разобрался.
Ну наконец разобрался.
Проблема была в том, в какой именно функции что делать.
Ответ для меня пришёл такой:
В MY_MODULE_form_node_form_alter нужно строить УРЕЗАННЫЙ список опций для POLE_2 (зависимого поля)только при первой загрузке формы.
(выборка опций для ранее сохранённого POLE_1 или для новой ноды, чтобы скрыть все опции POLE_2)
В остальных случаях (ajax сабмиты разными полями) вообще не трогать набор зависимого поля и выпускать в кеш его МАКСИМАЛЬНО ШИРОКИМ.
В функции $element[#ajax][callback], которая возвращает рендер массив элемента
нужно заниматься отборкой опций и скрытием ненужных.
То есть после построения кеша.
Напомню мой случай:
1 - я строил два зависимых поля - (оба ссылки на тремины таксономии разных словарей) - POLE_2 фильтровалось по введённому значению POLE_1
2 - При каждом сабмите из ajax мне нужно было строить массив опций для POLE_2, чтобы он находился в кеше
(иначе, если выбрать что-нибудь из POLE_2 и оно прилетит в следующем сабмите, то несовпадение с возможными
опциями из кеша даст ошибку "Недопустимый выбор. Обратитесь к администратору сайта.")
3 - но в форме есть куча других полей которые делают свои сабмиты, и они добавляют новые поля в форму
(а мои поля нет. Вот например image field - загрузил фотку и появились поля Заголовок и Alt)
данные из этих новых полей, прилетая при следующем сабмите на валидацию, отсутствовуют в кеше и вызывают error из пункта 2.
И чтобы этого не происходило им приходится перестраивать форму с помощью $form_state['rebuild'] = TRUE;
4 - при этом $form_state['values'] затирается и я не мог реализовать шаг 2 и хватал ошибку, так как пытался ограничить список опций ДО постройки кеша. То есть в MY_MODULE_form_node_form_alter.
5 - обошёл проблему: начал выпускать из MY_MODULE_form_node_form_alter максимально полный объём опций и скрывать ненужные в $element[#ajax][callback].
6 - т.е. если сабмитит не моё поле, то ограничения определённые в $element[#ajax][callback] не подхватываются и в кеше висит полный набор опций который проглотит любую выбранную опцию из POLE_2
Вот так то.
Не надеюсь особо, что кто-то захочет углубляться, но буду рад подсказать, если что.