Создание иерархических выпадающих списков на jQuery

Аватар пользователя kyky kyky 1 декабря 2008 в 13:51

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

Например, требуется последовательно задать для автомобиля марку и затем модель, то есть:

Toyota
-- Toyota Cresta
-- Toyota Marc
Audi
-- Audi Какой-то1
-- Audi Какой-то2

и так далее...

Дело в том, что недостаточно указать модель, поскольку бывает необходимость выбрать все автомобили какой-то марки, например все Тойоты. Этот случай касается в основном views, когда поиск ведется по расширенным фильтрам.

Можно использовать модуль hierarchical_select, осуществляя выбор по терминам, но проблема в том, что он весьма глючный (особенно в Опере) и нет стабильного релиза для шестерки.

А ведь можно написать простой скрипт, который выполнит роль этого модуля!

Для начала, создайте 2 словаря или 2 поля cck (да, можно и так и так -- в данном примере я буду рассказывать про поля cck, но для таксономии это аналогично). У меня поля:

field_auto_mark -- текстовое/селектбоксы, обязательно, допустимые значения:
AC
Acura
Alfa Romeo
Alpina
и так далее, все известные марки..

field_auto_model -- текстовое/селектбоксы, обязательно, допустимые значения:
AC ACE
AC Cobra
Acura CL
Acura EL
Acura Integra
Acura MDX
Acura NSX
Acura RL
Acura RSX
Acura SLX
Acura TL
Acura TSX
Alfa Romeo 1750
Alfa Romeo 2000
Alfa Romeo 33
Alfa Romeo 451

и так далее, то есть имя марки и через пробел модель

теперь создайте блок, установите формат ввода, поддерживающий тег script, а в теле блока напишите следующее:

<script type="text/javascript">
//эта функция будет вызвана, когда прогрузится страница
        $(document).ready(function(){
                update();//начальный вызов функции обновления списков
                $("select#edit-field-auto-mark-value").change(function({
//установка вызова функции обновления при смене значения сектбокса
                        update();
                });
        });

//собственно главная функция обновления списков:
        function update () {
                var token = "";//резервируем переменную

//получаем текстовое значение выбранного элемента родительского селектбокса, например это "Toyota"
                token= $("select#edit-field-auto-mark-value option:selected").text();

//прячем в дочернем селектбоксе все, что видимо...
                $("select#edit-field-auto-model-value > option:visible").hide();

//выводим те элементы дочернего селекбокса, в которых встречается наша "Toyota"
                $("option:contains(" + token + ")").show();

//устанавливаем для дочернего селектбокса выбранным первый видимый элемент, это первая из списка моделей всех Тойот
                $("select#edit-field-auto-model-value > option:visible:first").attr("selected", "selected");
        }
</script>

Этот блок повесте где-то в подвале, а по хорошему, конечно, лучше прописать в шаблоне материала или через шаблон темы.
Аналогично скрипт работает для таксономии и для views, когда пользователь выбирает exsposed filters. Достаточно просто поменять соответствующие id.

Видео, демонстрирующее выпадание списков, можно посмотреть здесь.

Комментарии

Аватар пользователя saken saken 12 декабря 2008 в 21:27

"kyky" wrote:
установите формат ввода, поддерживающий тег script

PHP code должен же поддерживать? а то что-то у меня не получилось

Аватар пользователя kyky kyky 14 декабря 2008 в 11:52

php тут не нужен. Вот только с прискорбием сообщаю, что данный метод работает ТОЛЬКО В ФАЙЕРФОКСЕ. Вот так... так что придумываю иные варианты...

Аватар пользователя Crazy Joker Crazy Joker 26 февраля 2009 в 13:45

Нашел ошибочку в коде (потеряна закрывающая скобка после function):
Было:

update();//начальный вызов функции обновления списков
$("select#edit-field-auto-mark-value").change(function({

Должно быть:

update();//начальный вызов функции обновления списков
$("select#edit-field-auto-mark-value").change(function(){ // <- Вот тут не хватало скобки

После исправления проверил в FF3, Opera 9.63 и IE6 - работает на УРА! Спасибо за snippet!

В догонку: я просто разместил скрипт в секции head в page.tpl.php

Аватар пользователя beerman beerman 26 февраля 2009 в 14:18

"Crazy Joker" wrote:
В догонку: я просто разместил скрипт в секции head в page.tpl.php

линейкой по рукам!

[ru-api=drupal_add_js]drupal_add_js[/ru-api]

а сама организация списков оставляет желать лучшего.
при наличии трех списков получим примерно такую картину:

[Toyota] [Toyota Camry] [Camry Prominent]

юзайте
hierarchical_select

Аватар пользователя Crazy Joker Crazy Joker 26 февраля 2009 в 18:15

Ну что ж сразу уж:

"beerman" wrote:
линейкой по рукам!

На этапе отладки скрипта вставлял в шаблон. На финальном этапе - добавил скрипт в .info файл темы.
Для моей задачи данное решение подошло как нельзя лучше!
Кстати, работает и с content_taxonomy field.

hierarhical_select для D6 еше сырой - не работает с content_taxonomy.

Аватар пользователя Crazy Joker Crazy Joker 1 марта 2009 в 4:10

С сожалением должен констатировать тот факт, что на большом количестве записей (в моем случае: около 500 в родительском списке, и около 3500 в подчиненном) браузеры начинают жутким образом тормозить при выполнении скрипта. Метод подходит только для небольших списков.

Кстати, проверил в Safari - тоже работает.

Аватар пользователя Dakascos@drupal.org Dakascos@drupal.org 4 июня 2009 в 17:23

"kyky" wrote:
теперь создайте блок, установите формат ввода, поддерживающий тег script, а в теле блока напишите следующее:

можно поподробнее?
"kyky" wrote:
Этот блок повесте где-то в подвале, а по хорошему, конечно, лучше прописать в шаблоне материала или через шаблон темы

и туточки
"kyky" wrote:
Аналогично скрипт работает для таксономии и для views, когда пользователь выбирает exsposed filters. Достаточно просто поменять соответствующие id.

и вот туточки))
Для новичка, можно разжевать? А то не знаю куда что вставлять... ничерта не получается((
Заранее ОГРОМНОЕ СПАСИБО!!!

Аватар пользователя kyky kyky 5 июня 2009 в 7:06

"<a href="mailto:Dakascos@drupal.org">Dakascos@drupal.org</a>" wrote:
Для новичка, можно разжевать?

Смысл? Метод работает только в фоксе и сафари, так что всё это напрасно.

Аватар пользователя Dakascos@drupal.org Dakascos@drupal.org 5 июня 2009 в 9:50

kyky wrote:
"<a href="mailto:Dakascos@drupal.org">Dakascos@drupal.org</a>" wrote:
Для новичка, можно разжевать?

Смысл? Метод работает только в фоксе и сафари, так что всё это напрасно.

Crazy Jocker писал: "После исправления проверил в FF3, Opera 9.63 и IE6 - работает на УРА! Спасибо за snippet!"

Так работает или нет? Непонятно...

Аватар пользователя kyky kyky 5 июня 2009 в 12:54

Я всё это дело разрабатывал в FF, а в осле и опере у меня не проканало.
Но раз уважаемый Crazy Joker исправил ошибку в коде и пишет, что всё нормуль, то, видимо, метод дейстительно претендует на использование.
Я проверю, дейстительно скрипт работает во всех браузерах, и если так, опишу подробнее.

Аватар пользователя Freakachoo Freakachoo 11 июня 2009 в 2:54

А как для таксономии называть переменные типа: edit-field-auto-mark-value ?
И подскажите пожалста как в таксономии разбивать не по 2-м словарям а по иерархии?

Мне нужно сделать в первом селекте список городов, а во втором - список станций метро. Думаю это нужно делать все через один словарь - просто дети для города будут станции метро...

Подскажите пожалуйста как через ваш скрипт реализовать - ооочень нужно! А hierarchical_select для 6-ки эту функцию еше не реализовал и написал что реализует только в августе Sad а нужно ведь щас!

Аватар пользователя slider@drupal.org slider@drupal.org 12 августа 2009 в 0:28

Freakachoo wrote:
А как для таксономии называть переменные типа: edit-field-auto-mark-value ?
И подскажите пожалста как в таксономии разбивать не по 2-м словарям а по иерархии?

Подумал, раз взялся - напишу про это. Я делал примерно так (этот код стоит в поле "Advanced settings for hierarchical vocabularies" > "Advanced PHP code" свойств поля cck):

<?php
$cities 
user_city(); 
//город у меня выбирается глобально, поэтому я вызываю свою функцию 
//для получения id, можно указать просто родительский термин - вместо $parent_term укажите id родителя.

$parent_term $cities['selected'];

$subCity taxonomy_get_tree(2$parent_term, -11); 
//см. Drupal API функции taxonomy_get_tree. "2" - это id словаря городов. 
//В них есть подиерархии: "Метро", "Районы" и т.п.

if (is_array($subCity)) {
   foreach (
$subCity as $t) {
      if (
$t->name == 'Метро'$parent_term $t->tid;
   }
}

return 

$parent_term;
?>

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

Словарь "Города"

-Санкт-петербург
--Метро
---Академическая
---Гражданский проспект
---...
--Районы
---Фрунзенский
---Невский
---...

Аватар пользователя Dakascos@drupal.org Dakascos@drupal.org 11 июня 2009 в 9:22

kyky wrote:

Этот блок повесте где-то в подвале, а по хорошему, конечно, лучше прописать в шаблоне материала или через шаблон темы.
Аналогично скрипт работает для таксономии и для views, когда пользователь выбирает exsposed filters. Достаточно просто поменять соответствующие id.

Все, конечно, работает, но вот в списке моделей выводятся все модели и, плюс ко всему, с рядом стоящей маркой соответствующего авто. Можно ли сделать так, чтобы выводидись только марки и только той модели, которая выбрана?

Аватар пользователя slider@drupal.org slider@drupal.org 11 августа 2009 в 11:46

Я думаю - можно, но (увы и ах) не знаю, как... Sad

Смысл в том, чтобы второй список был группирован по первому. Т.е. из примера автора поста будет так:

field_auto_mark

<select>
    <option>AC</option>
    <option>Acura</option>
    <option>Alfa Romeo</option>
    <option>Alpina</option>
</select>
и так далее, все известные марки..

field_auto_model

<select>
<optgroup label="AC">
    <option>ACE</option>
    <option>Cobra</option>
</optgroup>
    <optgroup label="Acura">
    <option>CL</option>
    <option>EL</option>
    <option>Integra</option>
    <option>MDX</option>
    <option>NSX</option>
    <option>RL</option>
    <option>RSX</option>
</optgroup>
</select>

...и т.п. Такой select можно сделать и просто как cck fields, так и через content taxonomy, построив их автоматически из иерархической таксономии.
Проблема в том, что я совсем не знаю jquery и с js в принципе не слишком хорошо знаком. Поэтому не знаю, как обращаться в optgroup, вместо option.
Если кто-то знает как (или может быть знает, почему "никак") - может быть поможете? Думаю, такие списки решат и функциональную и эстетическую часть задачи.

Аватар пользователя slider@drupal.org slider@drupal.org 12 августа 2009 в 0:30

Не могу понять, то ли моих мессаг не видно, то ли модераторы здесь больно злые. Ладно, напишу так, для собственной памяти. Может быть кому-то и пригодится.

Решать такую задачу через hierarchical select довольно накладно, да еще и не работает пока что так как надо с views... в общем, предлагаю считать развитием мысли kyky:

Как создать content taxonomy field с полями n-го словаря второго уровня таксономии, я здесь описывать не буду. Скажу только, что (насколько я знаю) с версии 6.x-2.5 можно сделать спискок выбора, используя тег optgroup, примерно так:

<?php
$select_array 
= array(
  
'фрукты' => array(
    
=> 'яблоки',
    
=> 'бананы',
    
=> 'апельсины',
  ),

  

'овощи' => array(
    
=> 'томаты',
    
=> 'баклажаны',
    
=> 'огурцы',
  ),
);

return 

$select_array;
?>

Т.о., у нас получатся два списка такого вида (ненужное вырезал):

<select id="edit-field-test1-value">
     <option value="фрукты">фрукты</option>
     <option value="овощи">овощи</option>
</select>

<select id="edit-field-test-value">
     <optgroup label="фрукты">
          <option value="1">яблоки</option>
          <option value="2">бананы</option>
          <option value="3">апельсины</option>
     </optgroup>
     <optgroup label="овощи">
          <option value="4">томаты</option>
          <option value="5">баклажаны</option>
          <option value="6">огурцы</option>
</optgroup>
</select>

Сам скрипт будет примерно таким:

$(document).ready(function(){
    update();
    $("select#edit-field-test1-value").change(function(){
        update();
    });
});

function update () {
   var token = "";
   token = $("select#edit-field-test1-value option:selected").text();

$("select#edit-field-test-value > optgroup[label!='"+token+"'] > option").attr("disabled","disabled");
$("select#edit-field-test-value > optgroup[label='"+token+"'] > option").removeAttr("disabled");
$("select#edit-field-test-value > optgroup[label!='"+token+"']").hide();
$("select#edit-field-test-value > optgroup[label='"+token+"']").show();

$("select#edit-field-test-value > optgroup[label='"+token+"'] > option:visible:first").attr("selected", "selected");
}

Небольшой коммент:
attr("disabled","disabled"); и removeAttr("disabled");
это потому, что по крайней мере в Opera 9.64 и 9.62 hide() и show() для optgroup не отрабатывается. Не было еще возможности проверить в IE... В Firefox 3 все срабатывает хорошо (завтра потестирую на больших списках). Следовательно, либо мы получаем во втором селекте только нужный optgroup со списком option, либо (в случае с Opera) - полный список, но с разрешенными option только нужного optgroup, не давая возможности выбрать неподходящее значение. В случае, если javascript отключен - списки сохраняются.

P.S. Это только у меня предпросмотр коммента не работает?