[РЕШЕНО] Дерзкая друпаловская AJAX кнопка "Add More" в форме ноды, которая не хочет сабмитить значения остальных полей формы.

Главные вкладки

Аватар пользователя Владислав888 Владислав888 4 апреля 2014 в 19:16

Обед задержан на 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 функции

Проблема подкралась в кеше форм
Судя по этому комменту с друпалорга и букварю Томлинсона и Вандюка при создании и использовании динамических полей в форме порядок работы такой:

  1. Юзер получает доступ к форме
  2. Строится PHP массив формы
  3. Сохраняется именно тот набор полей и опций который будет отправлен в броузер и прикрепляется уникальный ключ
  4. Юзер изменяет POLE-1 провоцируя Ajax сабмит (внимание) ВСЕХ данных формы
  5. Друпал сравнивает по уникальному ключу пришедший набор полей и опций с кешем, который сохранил в пункте 3 и (ЗДЕСЬ ГВОЗДЬ) генерит ошибку если набор отличается, чтобы хакеры не подпихивали свои поля к форме.
  6. строит форму по новой и даёт изменить её моему модулю.
  7. сохраняет кэш формы снова по пункту 3
  8. передаёт форму в callback функцию чтобы та только вернула определённый элемент, который нужно заменить в форме. Иначе, если изменить форму в callback функции, сравнение с кешем в пункте 5 не пройдёт.
  9. Юзер сабмитит форму и она опять сравнивается с кешем, который создавался при 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
Всем добра.

Комментарии

Аватар пользователя Владислав888 Владислав888 4 апреля 2014 в 21:40

«Самый крутой модуль Максима Подорова http://drupal.org/project/ddf, решает вашу задачу мышкой.»
Благодарю, Александр!
Сам же сто раз читал, что на всё есть модуль. Ох уж эта тяга ковыряться самому...
Завтра опробую.

Аватар пользователя alextdk alextdk 4 апреля 2014 в 22:13

Попробуйте, но возможно это не совсем то, что вам нужно, но модуль может делать зависимости двух entityreference филдов, с передачей параметров в другой ... мне честно говоря неохота было до конца вникать в вашу проблему Smile ... а что касается ковыряний, так в этом нет ничего плохого, судя по тому что я смог уловить, вы довольно плохо знаете FORM API и как он работает, а эта задача хороший повод исправить такое положение.

Аватар пользователя Владислав888 Владислав888 5 апреля 2014 в 19:23

Александр, да мне до нормального знания 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 его нет.

Аватар пользователя Владислав888 Владислав888 5 апреля 2014 в 21:06

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... сижу разбираюсь

Аватар пользователя Владислав888 Владислав888 5 апреля 2014 в 22:00

Кажется я начинаю понимать в чём косяк...
field_add_more_submit в конце ставит $form_state['rebuild'] = TRUE;
форма перестраивается с дефолтными значениями и пустым $form_state...

Но проблема наверно в подходе к изменению формы...
Сейчас опробую и отпишусь...

Аватар пользователя Владислав888 Владислав888 6 апреля 2014 в 0:13

Без результата пока что, но двигаюсь в направлении таком:
Насколько я понял
в функции 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 не разобрался.

Аватар пользователя Владислав888 Владислав888 6 апреля 2014 в 16:29

Ну наконец разобрался.

Проблема была в том, в какой именно функции что делать.

Ответ для меня пришёл такой:

В 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

Вот так то.
Не надеюсь особо, что кто-то захочет углубляться, но буду рад подсказать, если что.