За последние 24 часа нас посетили 22368 программистов и 1279 роботов. Сейчас ищут 758 программистов ...

Зависимые select-поля формы (как на авито)

Тема в разделе "Прочие вопросы по PHP", создана пользователем Swapf, 2 июн 2014.

  1. Swapf

    Swapf Активный пользователь

    С нами с:
    8 окт 2012
    Сообщения:
    82
    Симпатии:
    0
    Адрес:
    Россия, Самарская обл, Сызрань
    Нужно сделать форму поиска с динамически зависимыми полями. Как на авито, например. Т.е. выбираешь, например, категорию авто - тут же асинхронно появляются другие поля - новые авто, подержанные авто и т.д. Выбираешь из этих полей новые авто - асинхронно добавляются еще штук 5 полей, это могут быть марка, модель и т.д. Глубина вложенности неограниченная, но реально не более 5.

    Что есть сейчас.

    Есть 2 таблицы.
    Код (Text):
    1. CREATE TABLE `tbl_option` (
    2.     `opt_id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
    3.     `opt_value` INT(11) UNSIGNED NOT NULL,
    4.     `opt_name` VARCHAR(128) NOT NULL,
    5.     `opt_parent` INT(11) UNSIGNED NOT NULL,
    6.     `opt_cat_id` INT(11) UNSIGNED NOT NULL,
    7.     `opt_order` INT(4) UNSIGNED NOT NULL,
    8.     PRIMARY KEY (`opt_id`)
    9. )
    10. INSERT INTO `tbl_option` (`opt_id`, `opt_value`, `opt_name`, `opt_parent`, `opt_cat_id`, `opt_order`) VALUES (1, 1, 'Марка', 0, 2, 1);
    11. INSERT INTO `tbl_option` (`opt_id`, `opt_value`, `opt_name`, `opt_parent`, `opt_cat_id`, `opt_order`) VALUES (2, 2, 'Модель', 1, 2, 2);
    12. INSERT INTO `tbl_option` (`opt_id`, `opt_value`, `opt_name`, `opt_parent`, `opt_cat_id`, `opt_order`) VALUES (3, 3, 'Год выпуска', 0, 2, 3);
    13. INSERT INTO `tbl_option` (`opt_id`, `opt_value`, `opt_name`, `opt_parent`, `opt_cat_id`, `opt_order`) VALUES (4, 4, 'Зависит от модели', 2, 2, 4);
    Это таблица содержит id опций, их название, id родителей, которым они принадлежат.
    opt_cat_id - это идентификатор категорий авто и транспорта.

    А это - то какие значения они могу принимать. Все это option'ы.
    Код (Text):
    1. CREATE TABLE `tbl_dataset` (
    2.     `ds_id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
    3.     `ds_value` INT(11) UNSIGNED NOT NULL DEFAULT '0',
    4.     `ds_name` VARCHAR(128) NOT NULL DEFAULT '0',
    5.     `ds_option_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
    6.     `ds_parent` INT(11) UNSIGNED NOT NULL DEFAULT '0',
    7.     `ds_order` INT(11) UNSIGNED NOT NULL DEFAULT '0',
    8.     PRIMARY KEY (`ds_id`)
    9. )
    10. INSERT INTO `tbl_dataset` (`ds_id`, `ds_value`, `ds_name`, `ds_option_id`, `ds_parent`, `ds_order`) VALUES (1, 1, 'AC', 1, 0, 1);
    11. INSERT INTO `tbl_dataset` (`ds_id`, `ds_value`, `ds_name`, `ds_option_id`, `ds_parent`, `ds_order`) VALUES (2, 2, 'Acura', 1, 0, 2);
    12. INSERT INTO `tbl_dataset` (`ds_id`, `ds_value`, `ds_name`, `ds_option_id`, `ds_parent`, `ds_order`) VALUES (3, 3, 'Alfa Romeo', 1, 0, 3);
    13. INSERT INTO `tbl_dataset` (`ds_id`, `ds_value`, `ds_name`, `ds_option_id`, `ds_parent`, `ds_order`) VALUES (4, 4, 'Ace', 2, 1, 1);
    14. INSERT INTO `tbl_dataset` (`ds_id`, `ds_value`, `ds_name`, `ds_option_id`, `ds_parent`, `ds_order`) VALUES (5, 5, 'Acece', 2, 1, 2);
    15. INSERT INTO `tbl_dataset` (`ds_id`, `ds_value`, `ds_name`, `ds_option_id`, `ds_parent`, `ds_order`) VALUES (6, 6, 'Cobra', 2, 1, 3);
    16. INSERT INTO `tbl_dataset` (`ds_id`, `ds_value`, `ds_name`, `ds_option_id`, `ds_parent`, `ds_order`) VALUES (7, 7, 'принадлежит марке Acura', 2, 2, 1);
    17. INSERT INTO `tbl_dataset` (`ds_id`, `ds_value`, `ds_name`, `ds_option_id`, `ds_parent`, `ds_order`) VALUES (8, 8, 'Значение опции - только для модели Acece', 4, 5, 1);
    ds_option_id - указывает какой опции opt_parent из первой таблице принадлежит запись.
    ds_parent - говорит о том, что это значение принадлежит значениям ds_id. Например, марка AC имеет модели Ace, Acece, Cobra.

    При изменении значения форма сериализуется и POSTом отправляется в контроллер. Там обрабатывается и возвращается уже html-код, который вставляется в DIV. Это все получается, но не очень правильно.

    Это сама форма
    Код (Text):
    1. <script type="text/javascript">
    2.  
    3. jQuery(function($)
    4. {
    5. $('body').on('change','.opt',
    6. function()
    7. {
    8.     //alert($('#search-model').serialize());
    9.     jQuery.ajax({'type':'POST','dataType':'json','data':$('#search-model').serialize(),'success':function(data){
    10.                     if(true){
    11.                         $('#dyn-flds').html(data.res);
    12.                         $('#v').html(data.res1);
    13.                     }else{
    14.                     }
    15.     },'url':'/index.php/site/search','cache':false});return false;
    16. });
    17.  
    18. });
    19. </script>
    20. <div id="v"></div>
    21. <div class="form-actions" style="">
    22. <?php
    23.  
    24. $form=$this->beginWidget('bootstrap.widgets.TbActiveForm', array(
    25.     'id'=>'search-model',
    26.     'type'=>'horizontal addcomment-form',
    27. ));
    28. ?>
    29. <div>
    30.         <?php echo $form->labelEx($model, 'cat'); ?>
    31.         <?php
    32.         $cat_data = Cat::model()->findAll();
    33.         $cat_data = CHtml::listData($cat_data, 'cat_value', 'cat_name');
    34.         ?>
    35.         <?php echo $form->dropDownList($model, 'cat', $cat_data, array('class'=>'opt')); ?>
    36.         <?php echo $form->error($model, 'cat'); ?>
    37.         <div id="dyn-flds">
    38.         </div>
    39. </div>
    40.  
    41. <?php $this->endWidget(); ?>
    42. </div><!-- form -->
    А это функция, которая занимается асинхронной обработкой
    Код (Text):
    1. public function actionSearch()
    2.         {
    3.             $search = $_POST['SearchForm'];
    4.             $option_value = $_POST['Option'];
    5.            
    6.             if(count($option_value))
    7.             foreach($option_value as $o_key => $o_val)
    8.             {
    9.                 $d[] = $o_key."->".$o_val;
    10.             }
    11.  
    12.             if(count($d))
    13.             {
    14.                 $d = implode(", ", $d);
    15.             }
    16.  
    17.             $options = Option::model()->findAll('opt_cat_id = '.$search['cat']);
    18.             $this->options = $options;
    19.             $this->option_value = $option_value;
    20.            
    21.             foreach ($options as $o)
    22.             {
    23.                 foreach($o->dataset as $d) //$o->dataset - массив объектов значений для тек. опции
    24.                 {
    25.                     if($option_value[$o->opt_value] == $d->ds_parent)
    26.                     {
    27.                     $id = $d->ds_value;
    28.                     $parent_id = $d->ds_parent;
    29.                     $this->data[$id] = $d;
    30.                     $this->index[$parent_id][] = $id;
    31.                    
    32.                     $data .= $d->ds_value."<br>";
    33.                     }
    34.                 }
    35.             }
    36.            
    37.             foreach($options as $o)
    38.             {
    39.                 $arr = null;
    40.                 if($o->opt_parent == 0)
    41.                 {
    42.                 $arr = CHtml::listData($o->dataset, 'ds_value', 'ds_name');
    43.                 $data .= CHtml::dropDownList('Option['.$o->opt_value."]", $option_value[$o->opt_value], $arr, array('prompt'=>$o->opt_name, 'class'=>'opt', 'cat'=>$o->opt_cat_id));
    44.                 }
    45.                 else
    46.                 {
    47.                     $dataset = $o->dataset;
    48.                     if(count($dataset))
    49.                     $dd = null;
    50.                     foreach ($dataset as $d)
    51.                     {
    52.                         if($option_value[$o->opt_parent] == $d->ds_parent)
    53.                         {
    54.                             $dd[$d->ds_value] = $d->ds_name;
    55.                         }
    56.                        
    57.                     }
    58.                     if(count($dd))
    59.                     {
    60.                         $data .= CHtml::dropDownList('Option['.$o->opt_value."]", $option_value[$o->opt_value], $dd, array('prompt'=>$o->opt_name, 'class'=>'opt'));
    61.                     }
    62.                 }
    63.             }
    64.            
    65.             echo CJSON::encode(array('res'=>$data, 'res1'=> $d));
    66.         }
    При выборе марки - появляются модели. Тут все ок. При выборе модели ничего не происходит, за исключением когда выбрана Acece - тогда появляется доп. поле Зависит от модели.
    Т.е. при выбранyой марке AC и модели Acece есть поле Зависит от модели. И вот теперь если сменить марку, тол это поле останется, а должно пропасть. Это из-за того, что когда сбрасывается марка и форма отправляется, то модель-то остается и в общем я себе уже всю голову сломал.

    Очень нужна Ваша помощь. 3 дня уже туплю - ничего придумать не могу.
    PS Все делается на Yii, но это не суть важно. проблема в алгоритме.
     
  2. Zuldek

    Zuldek Старожил

    С нами с:
    13 май 2014
    Сообщения:
    2.381
    Симпатии:
    344
    Адрес:
    Лондон, Тисовая улица, дом 4, чулан под лестницей
    Это потому что не хотите упростить себе задачу и стараетесь удержать в голове информацию о селектах и форме.
    Задачи нужно упрощать.
    Абстрагируйтесь от ваших марок, моделей и селектов html-формы.

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

    При выборе конкретного узла дерева вы должны:
    1. получить всех его родителей и их дочек первого уровня вложенности
    2. получить дочек вашего листочка первого уровня вложенности.
    3. убрать все остальные элементы дерева не относящиеся к выборке

    Теперь вернёмся в вашу практическую плоскость.
    Действия приложения должны быть примерно такими:
    1. Передаём выбранный id поля в контроллер, получая структурированный массив полей (из бд или кэша)
    2. Проверяем наличие этих полей в DOM, рендерите те, которых нет и делаете remove всех остальных.
    3. Не забываем продумать кэширование, правильный формат передачи данных клиенту, возможное хранение всех полей в сессиях или jsone на клиенте (почему бы и нет: всё зависит от конкретной реализации. Если их 2-3 десятка и они не заводятся "на лету", то смысла мучить сервер нам нет).

    Передавать по ajax htmlи при визуализации любых вложенных множеств, имхо, не по феншую.
     
  3. Fell-x27

    Fell-x27 Суперстар
    Команда форума Модератор

    С нами с:
    25 июл 2013
    Сообщения:
    12.155
    Симпатии:
    1.769
    Адрес:
    :сердА
    Эмм..а вы предлагаете генерить полный набор и отсекать лишнее перед отдачей клиенту? Сдается мне, это не совсем рилтаймовое решение.
     
  4. Swapf

    Swapf Активный пользователь

    С нами с:
    8 окт 2012
    Сообщения:
    82
    Симпатии:
    0
    Адрес:
    Россия, Самарская обл, Сызрань
    Кто-нибудь решал подобную задачу? Я имею в виду реально, а не теоретически. Там не все так просто(по крайне мере для меня).
     
  5. Zuldek

    Zuldek Старожил

    С нами с:
    13 май 2014
    Сообщения:
    2.381
    Симпатии:
    344
    Адрес:
    Лондон, Тисовая улица, дом 4, чулан под лестницей
    Нет. Я против именно использования html для транспортных задач, а не риалтайма и ajax-запросов.
    Передавал бы в jsone и рендерил бы изменяющиеся ветки дерева js-oм на клиенте. Против именно потому, что мы строим вложенные множества. Если использовать для передачи этих структур от сервера-клиенту html (веду речь не о начальной загрузке страницы а о запросах на дополнительные элементы дерева), то html должен будет содержать лишний код, ответственный за позиционирование передаваемого элемента множества.
    А то, какую часть дерева передавать по ajax-запросам: всю или видимую, или вообще передавать только id-шники которые нужно скрыть и добавить, а всё дерево изначально держать в jsone на клиенте с момента загрузки страницы — это уже субъективные детали которые от конкретного проекта зависят.
    Мы же с вами не знаем какое количество полей, их вложенность и изменчивость при каждом выборе узла дерева.

    Как удобнее так и можно делать. Удобнее передавать готовый html чтобы не возится с разбором в html jsona — ради Бога, можно и так.

    Тов. Swapf, вы или невнимательно читали или не вникли в суть поста. Вашу задачу решали практически все программисты. Любой кто имел дело с визуализацией вложенного меню на JS. Отличай заключается лишь в получении узлов дерева от сервера по xmlhttprequest.
    То, как должна быть решена задача вам объяснили.
    Если вы хотите само решение задачи для коммерческого проекта за вас, то так и нужно формулировать свой пост: "сделайте за меня то-то и то-то..".
    Что не просто или что вам осталось непонятно из описанного алгоритма действий?
     
  6. Fell-x27

    Fell-x27 Суперстар
    Команда форума Модератор

    С нами с:
    25 июл 2013
    Сообщения:
    12.155
    Симпатии:
    1.769
    Адрес:
    :сердА
    Теперь понял. Ну, разница, по факту, лишь в количестве передаваемых данных, которых, на деле, вряд ли будет очень много.
    Даже по трудозатратам одинаково. Что тянуть данные из БД и строить HTML в пхп, чтобы отдать клиенту, что из тех же данных, обернутых в JSON строить HTML в браузере. Единственное, что в случае с генерацией на стороне сервера можно предусмотреть грейсфул деградейшн, если оказалось, что браузер не может в JS. Тогда сервер сможет отдать полностью готовую анкету. Да, там будут "избыточные" поля, которые придется оставить пустыми в ряде случаев, но, зато, будет работать.
     
  7. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    чувак. куда уж проще: выбрали - запросил, нарисовал.
     
  8. Swapf

    Swapf Активный пользователь

    С нами с:
    8 окт 2012
    Сообщения:
    82
    Симпатии:
    0
    Адрес:
    Россия, Самарская обл, Сызрань
    Неужели это настолько просто??? ... ушел пробовать. Видимо, я что-то не понимаю...
     
  9. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    вешаешь на ончендж или типа того обработчик. в обработчике смотришь значение или какой-нить дата-атрибут того, чего выбрали. делаешь запрос аяксом. получаешь ответ. пихаешь в нужном виде в нужное место.

    соотв обработчик можно навесить так, чтобы он динамически подцепился на новый хтмл код.
     
  10. Zuldek

    Zuldek Старожил

    С нами с:
    13 май 2014
    Сообщения:
    2.381
    Симпатии:
    344
    Адрес:
    Лондон, Тисовая улица, дом 4, чулан под лестницей
    Коллеги, поправьте меня если я ошибаюсь (разработка под web не родная стезя), но, насколько я помню, поддержка xmlhttprequest в браузерах появилась позже поддержки работы с форматом json и функциями управления элементами DOM, которые используются для решения задачи.
    Если полностью отказываться от запросов к серверу за узлами дерева и отдавать все структурированные множества сразу может получиться слишком большой избыток лишних данных. Если к примеру мы ведём речь о 200 полях формы (не рассуждения о сферическом коне, буквально недавно был проект по недвижимости именно с такой задачей) из которых активных (выбранных пользователем) лишь 10, то остальные данные будут всё-таки лишними. А ведь поля могут быть ещё и с данными... .

    Как для хранения данных (даже если отдаём клиенту всё дерево при запросе страницы разом) так и для аякс-запросов я сторонник использования именно json потому что:
    1. MVC-фетиш. Представление запрашивает у контроллера данные и должно получить данные, а не трусы в горошек.
    2. Данные в json всё-таки заметно легче
    3. Данные структурированы и ими легко оперировать если у вас предвидятся с ними дальнейшая динамика на стороне клиента.
    4. Ну и приятнее поддерживать приложения в которых контроллеры не занимаются генерацией хтмелей. Тем более если тс делает проект на прогрессивном фреймворке. Другой разработчик который потом будет работать с его приложением ему же за это спасибо скажет.
     
  11. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    хтмл отдавать или данные для генерации хтмл на клиенте - зависит только от того, что удобнее: данные или хтмл =)
     
  12. Zuldek

    Zuldek Старожил

    С нами с:
    13 май 2014
    Сообщения:
    2.381
    Симпатии:
    344
    Адрес:
    Лондон, Тисовая улица, дом 4, чулан под лестницей
    Так то оно так, но вот лежит у меня файлик с jsonoм как раз подобных задаче полей формы из отраслевого портала. Весом 500 кб и я подозреваю, что разница с html-версией будет весьма значительной =)
    При этом, файлик то у юзеров в кэше, лежит никого не трогает, и для клиентского интерфейса нам даже не нужны дополнительные аяксы. А на бэкенде-то под них запрос на 3 таблицы: попроси субд, да сшей, да отдай и это ещё без прочих запросов для странички, которые СУБД обслуживает. Мне и коллеги и руководитель проекта до такой реализации говорили "Диман, ты заморачиваешься, и так работает", но это пока запросы к личному кабинету на создание нод не стали поступать сотнями. В общем всё субъективно, главное помнить что где уместно =)
     
  13. Fell-x27

    Fell-x27 Суперстар
    Команда форума Модератор

    С нами с:
    25 июл 2013
    Сообщения:
    12.155
    Симпатии:
    1.769
    Адрес:
    :сердА
    Я не зря сказал про "грейсфул деградейшн". Суть этого подхода в том, чтобы "если что-то, по каким-то причинам, не может работать как надо, пусть работает хоть как-нибудь, лишь бы работало".