Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > JavaScript: Наши скрипты > Автоматический мультиселект опций в списке выбора


Автор: 12345c 10.4.2006, 23:49
 Предположим, что имеется задача создать двух- или более уровневое меню в списке <select> и при этом нужно подчеркнуть, что выбор раздела означает выбор всех опций раздела - выделить все опции, но не вручную.

Например, на странице "http://forum.vingrad.ru/index.php?act=Search&f=10"  этого форума есть такое меню. В нём можно сделать как одиночный, так и множественный выбор. Есть в нём и выбор раздела, при котором автоматического выделения подразделов не происходит, но подразумевается. И вот, мы захотели указать это явно.

Понятно, что тег <optgroup label="Раздел ХХ">  не поможет, потому что он приводит к невозможности выделения строки раздела в списке (которая выделена жирным наклонным шрифтом). Зато, строки приходится как-то выделять по отношению к названиям разделов. Воспользуемся этим признаком, чтобы обнаружить, попал выбор на раздел или на обычный пункт. (Можно, конечно, опциям приписать скрытые признаки, но это будет дублирование явных признаков.)

Для двухуровневого списка с выделением пунктов специальными символами код для группового выделения раздела будет такой:

Код

<select size="8" multiple onchange="ff(this)"><option></option>
<option>Раздел 1</option>
<option>• Пункт1</option>
<option>• Пункт2</option>
<option>Раздел 2 </option>
<option>• Пункт1 (р.2)</option>
<option>• Пункт2 (р.2)</option>
<option>• Пункт3 (р.2)</option>
</select>
<script>
ff=function(t){        //групповое выделение опций в списке выбора
    var i=t.selectedIndex;
    if(t.options[i].text.indexOf('• ')>=0)return;
    while(i<t.options.length-1&&t.options[++i].text.indexOf('• ')>=0)
        t.options[i].selected=!0;}
</script>
Если кликнуть по пункту, то выбор будет обычным, если по разделу - выберутся заодно все его пункты.

Вот как выглядел бы скрипт для выбора групп подразделов форума (скопированный со страницы поиска и урезанный трёхуровневый список). Способ выделения уровней другой, поэтому скрипт несколько другой.

Код
<style>
    .cat{font-weight:bold;color:crimson;}
    .sub{font-weight:bold;color:blue;}</style>
<script>
ff2=function(t){        //групповое выделение опций в списке выбора
    var c=unescape("%B7%B7%A0");          //строка "&middot;&middot;&nbsp;" в JS
    var i=t.selectedIndex;
    var j;
    if((j=t.options[i].text.indexOf(c))>0)return;
            //j - признак уровня, -1 или 0
    while(i++<t.options.length            //если не конец
      &&(t.options[i].text.indexOf(c)>=0&&j==-1  //и не 1-й уровень
        ||t.options[i].text.indexOf(c)>=2&&j==0)  //или не 2-й уровень
    )t.options[i].selected=!0;}
</script>
<select size="20" multiple onchange="ff2(this)">
<option value='all' selected="selected">&raquo; Все форумы</option><option value="c_1" class='cat'>Программирование</option>
<option value="1" class='sub' >&middot;&middot;&nbsp;C ++ (Подкатегория)</option>
<option value="101">&middot;&middot;&middot;&middot;&nbsp;C/C++: Программирование под Unix/Linux</option>
<option value="100">&middot;&middot;&middot;&middot;&nbsp;C/C++: COM/DCOM/ActiveX/ATL/CORBA</option>
<option value="2" class='sub' >&middot;&middot;&nbsp;Delphi, Kylix and Pascal (Подкатегория)</option>
<option value="84">&middot;&middot;&middot;&middot;&nbsp;Delphi: Общие вопросы</option>
<option value="49">&middot;&middot;&middot;&middot;&nbsp;Kylix</option>
<option value="9" class='sub' >&middot;&middot;&nbsp;Java (Подкатегория)</option>
<option value="112">&middot;&middot;&middot;&middot;&nbsp;Java tools</option>
<option value="107">&middot;&middot;&middot;&middot;&nbsp;Java: GUI приложения</option>
<option value="108">&middot;&middot;&middot;&middot;&nbsp;Java: Работа с сетью</option>
<option value="12" class='sub' >&middot;&middot;&nbsp;Базы данных (Подкатегория)</option>
<option value="153">&middot;&middot;&middot;&middot;&nbsp;Общие вопросы по базам данных</option>
<option value="168">&middot;&middot;&middot;&middot;&nbsp;Advantage Database Server</option>
<option value="169">&middot;&middot;&middot;&middot;&nbsp;Paradox</option>
<option value="115" class='sub' >&middot;&middot;&nbsp;Красная книга (Подкатегория)</option>
<option value="116">&middot;&middot;&middot;&middot;&nbsp;Python</option>
<option value="c_5" class='cat'>Разработка систем для Web</option>

<option value="6" class='sub' >&middot;&middot;&nbsp;PHP (Подкатегория)</option>
<option value="176">&middot;&middot;&middot;&middot;&nbsp;PHP для начинающих</option>
<option value="209">&middot;&middot;&middot;&middot;&nbsp;PHP: работа с текстовой информацией</option>
<option value="177">&middot;&middot;&middot;&middot;&nbsp;PHP: работа с готовыми скриптами и тестирование</option>
<option value="5" class='sub' >&middot;&middot;&nbsp;Perl (Подкатегория)</option>
<option value="183">&middot;&middot;&middot;&middot;&nbsp;Perl: CGI программирование</option>
<option value="182">&middot;&middot;&middot;&middot;&nbsp;Perl: Системное программирование</option>
<option value="15" class='sub' >&middot;&middot;&nbsp;Flash platform (Подкатегория)</option>
<option value="217">&middot;&middot;&middot;&middot;&nbsp;Flash</option>

<option value="7">&middot;&middot;&nbsp;ASP</option>
<option value="10" class='sub'>&middot;&middot;&nbsp;JavaScripts (Подкатегория)</option>
<option value="74">&middot;&middot;&middot;&middot;&nbsp;JavaScripts для начинающих</option>
<option value="188">&middot;&middot;&middot;&middot;&nbsp;JavaScripts: Наши скрипты</option>
<option value="122">&middot;&middot;&nbsp;XML, XSL</option>
<option value="18">&middot;&middot;&nbsp;Web технологии</option>
<option value="221">&middot;&middot;&nbsp;TYPO3</option>
<option value="c_6" class='cat'>Технологии и алгоритмы</option>
<option value="13">&middot;&middot;&nbsp;Алгоритмы</option>
<option value="11" class='sub' >&middot;&middot;&nbsp;Технологии (Подкатегория)</option>
<option value="59">&middot;&middot;&middot;&middot;&nbsp;Методы борьбы со спамом</option>
<option value="165">&middot;&middot;&middot;&middot;&nbsp;Системный анализ, проектирование и UML</option>
<option value="c_7" class='cat'>Компьютерные системы</option>
<option value="28">&middot;&middot;&nbsp;MS Windows</option>
<option value="29" class='sub' >&middot;&middot;&nbsp;Linux/Unix (Подкатегория)</option>

<option value="135">&middot;&middot;&middot;&middot;&nbsp;*NIX системы: Общие вопросы</option>
<option value="21">&middot;&middot;&nbsp;Программное обеспечение</option>
<option value="132" class='sub' >&middot;&middot;&nbsp;Звук, графика и видео (Подкатегория)</option>
<option value="14">&middot;&middot;&middot;&middot;&nbsp;Photoshop, Corel и т.п.</option>
<option value="133">&middot;&middot;&middot;&middot;&nbsp;Работа со звуком</option>
<option value="134">&middot;&middot;&middot;&middot;&nbsp;Работа с видео</option>
<option value="c_8" class='cat'>Работа, хобби и учёба</option>
<option value="141" class='sub' >&middot;&middot;&nbsp;Vingrad - Колледж (Подкатегория)</option>
<option value="144">&middot;&middot;&middot;&middot;&nbsp;Куда пойти учиться?</option>
<option value="25" class='sub' >&middot;&middot;&nbsp;Работа (Подкатегория)</option>
<option value="148">&middot;&middot;&middot;&middot;&nbsp;Обсуждение работы</option>
<option value="42">&middot;&middot;&nbsp;Совместные проекты/поиск партнёров</option>
<option value="20" class='sub' >&middot;&middot;&nbsp;Готовые и разрабатываемые проекты (Подкатегория)</option>
<option value="80">&middot;&middot;&middot;&middot;&nbsp;Чтиво</option>
<option value="145">&middot;&middot;&middot;&middot;&nbsp;Наши конкурсы</option>
<option value="52" class='sub' >&middot;&middot;&nbsp;Наши проекты (Подкатегория)</option>

<option value="189">&middot;&middot;&middot;&middot;&nbsp;Vingrad CMS</option>
<option value="62">&middot;&middot;&middot;&middot;&nbsp;Delphi Russian Knowledge Base</option>
<option value="c_3" class='cat'>Разное</option>
<option value="214" class='sub' >&middot;&middot;&nbsp;Наши тесты (Подкатегория)</option>
<option value="215">&middot;&middot;&middot;&middot;&nbsp;Продумывание тестов</option>
<option value="45" class='sub' >&middot;&middot;&nbsp;Ответы на часто задаваемые вопросы по программированию (Подкатегория)</option>
<option value="56">&middot;&middot;&middot;&middot;&nbsp;Java</option>
<option value="31">&middot;&middot;&middot;&middot;&nbsp;С++</option>

<option value="40">&middot;&middot;&middot;&middot;&nbsp;Программирование контроллеров</option>
<option value="193">&middot;&middot;&nbsp;Флейм</option>
<option value="222" class='sub' >&middot;&middot;&nbsp;Новости IT (Подкатегория)</option>
<option value="241">&middot;&middot;&middot;&middot;&nbsp;Новости из мира Алгоритмов</option>
<option value="51">&middot;&middot;&middot;&middot;&nbsp;Новости из мира Vingrad</option>
<option value="150" class='sub' >&middot;&middot;&nbsp;Компьютерная литература (Подкатегория)</option>
<option value="171">&middot;&middot;&middot;&middot;&nbsp;Разыскивается</option>
<option value="24" class='sub' >&middot;&middot;&nbsp;Разные вопросы (Подкатегория)</option>
<option value="67">&middot;&middot;&middot;&middot;&nbsp;Научные дисскуссии</option>
<option value="71">&middot;&middot;&middot;&middot;&nbsp;Спорт</option>

<option value="68">&middot;&middot;&middot;&middot;&nbsp;Курилка (Политические споры)</option>
<option value="69">&middot;&middot;&middot;&middot;&nbsp;Религиозные войны</option>
<option value="140">&middot;&middot;&middot;&middot;&nbsp;Наши фото</option>
<option value="c_4" class='cat'>О нас</option>
<option value="197" class='sub' >&middot;&middot;&nbsp;Наши встречи (Подкатегория)</option>
<option value="198">&middot;&middot;&middot;&middot;&nbsp;Фотоотчёты наших встреч</option>
<option value="27" class='sub' >&middot;&middot;&nbsp;Обсуждение форума (Подкатегория)</option>
<option value="175">&middot;&middot;&middot;&middot;&nbsp;Книга отзывов о работе администрации</option>
</select>

Куда двигаться дальше?
Последний пример со сложным длинным двухуровневым списком разжигает аппетиты на усовершенствование скрипта группового выделения. Как показывает непродолжительная работа с результатом, возникают пожелания:

1) сейчас группа только выделяется, а хорошо бы, если бы выделенная группа 2-го уровня снимала выделение по клику на заголовке и удержании Ctrl.
2) если при выделенной группе и удержании Ctrl кликнуть по невыделенной группе выше, то происходит ожидаемое - выделяется 2-я группа, но если кликнуть по невыделенной группе, расположенной ниже - выделится только одна строка (IE, FF).

17.апр.2006 - html-часть примера значительно сокращена, чтобы тема быстрее загружалась.  

Автор: 12345c 11.4.2006, 02:35
3) Ctrl на фоне выделенной группы не снимает выделение.

(Поправка: этот список не двух-, а трёхуровневый.)

Все эти признаки вызваны, как можно догадаться, тем, что selectedIndex при Ctrl (и Shift) не меняет своего положения. Таким образом, в распоряжении нет свойства, показывающего, по какой option кликнули (onclick на option тоже не поддерживается). Для полного комфортного решения понадобится код гораздо большей сложности, эмулирующий обнаружение клика по элементу. Малым увеличением сложности можно обойтись, если просто не обрабатывать клики при нажатой Ctrl или Shift. С учётом замечаний код функции и её вызов становится таким:

Код
ff2=function(e,t){        //групповое выделение опций в списке выбора
    var c=unescape("%B7%B7%"+(self.opera?"20":"A0"));    //строка "&middot;&middot;&nbsp;" в JS
    var i=t.selectedIndex;
    var j;
    if(i<0||(j=t.options[i].text.indexOf(c))>0||cKey||sKey)return;
            //j - признак уровня, -1 или 0;  cKey, sKey - нажатость служ.клавиш
    while(i++<t.options.length        //если не конец
        &&(t.options[i].text.indexOf(c)>=0&&j==-1  //и не 1-й уровень
            ||t.options[i].text.indexOf(c)>=2&&j==0)  //или не 2-й уровень
    )t.options[i].selected=!0;
    }
</script>
<select size="20" multiple onchange="ff2(event,this)" onmousedown=cKey=event.ctrlKey;sKey=event.shiftKey;>
(изменения в числе параметров и в 6-й строчке)

Автор: Ciber SLasH 11.4.2006, 02:46
 Думаю так будет лучше smile 
здесь была 1-я версия скрипта... 

Автор: 12345c 11.4.2006, 03:02
Ciber SLasH, а в чём общий замысел? Ctrl иногда "прорывается" в твоём списке, когда не надо. Это происходит, когда вверху что-то выделено, а кликаешь ниже на заголовок группы. А если на группу сверху, то, наоборот, работает как обычное мультивыделение. Т.е. как противоположность моему первому коду. В моём 2-м коде Ctrl или Shift надёжно отрубают всякую попытку автоматики вмешаться.

Автор: Ciber SLasH 11.4.2006, 03:21
Цитата(12345c @ 11.4.2006, 04:02 Найти цитируемый пост)
Ciber SLasH, а в чём общий замысел?

Хотел сделать так, чтобы можно было выделять несколько групп, но не проверил как работает, если начинать снизу. smile

Автор: 12345c 11.4.2006, 03:46
Тут, всё же, несменность selectedIndex всёс портит. Причём, в FF это можно решить, т.к. он понимает клик на option, а в остальных - ...

Автор: Ciber SLasH 16.4.2006, 09:16
Вот, доделал скрипт:
здесь была 2-я версия скрипта... 

Автор: 12345c 17.4.2006, 02:08
(Не хватает пояснения, что этот  скрипт делает. Поясню для читателей.)

Скрипт Ciber SLasH делает групповое выделение подсписков, если кликнуть по заголовку раздела, выделенному в примере цветом. Кроме этого, если кликнуть по другому разделу, удерживая Ctrl, выделения прежней группы не пропадёт.

Заметил недостатки выделений. (Что неудивительно для сложной логики.)

1) если таким способом выделить 2 раздела, а потом кликнуть на одном из выделенных полей, вылетает ошибка (IE). По её причине, вероятно, повторный клик по разделу после ошибки не приводит к групповому автовыделению;
2) если, при Ctrl, убрать все выделения - ошибка;
 

Автор: Ciber SLasH 17.4.2006, 18:56
3-я версия...
Код
<script type='text/javascript'>
/*** Проверка вхождения элемента в массив ***/
Array.prototype.in_array = function (value) {
    for (var i = 0; i < this.length; i++) {
        if (this[i] == value) {
            return true;
        }
    }
return false;
}
////////////////////////////////////////////////////////////////////////////////
var ctrlKey = 0;    // состояние клавиши <Ctrl>
var selectedList = [];        // список текущих выделенных пунктов
// В Опере последний символ имеет значение %20, а в других бродилках — %A0
var c = window.opera ? unescape('%B7%B7%20') : unescape('%B7%B7%A0');
////////////////////////////////////////////////////////////////////////////////
/*** Регистрирование клавиш ***/
function regKey(ev) {
var evt = ev || window.event;
    ctrlKey = evt.ctrlKey;
}
/*** Групповое выделение опций в списке выбора ***/
function select(obj) {
var i = obj.selectedIndex;
var pr = false;    // признак необходимости обработать 4 и 5 состояние
    if (i == -1) {
        selectedList.splice(0, 1);
        return;
    } else {
        var opt = obj.options[i];
    }
    if (selectedList.length == 0) {    // 1
        opt.setAttribute('is_selected', 'yes');
        selectedList.push(i);
        pr = true;
    } else if (selectedList.in_array(i)) {    // 2,3
        var n = 0;
        while (n < selectedList.length) {
            opt = obj.options[ selectedList[n] ];
            if ( (selectedList[n] != i && !ctrlKey) || (ctrlKey && !opt.selected) ) {    // 2,3
                if (ctrlKey && !opt.selected) {    // 3
                    i = selectedList[n];
                }
                opt.setAttribute('is_selected', 'no');
                selectedList.splice(n, 1);
                n--;
                pr = true;
            }
            n++;
        }
    }
    if (!pr) {    // 4,5
        for (var n = 0; n < obj.options.length; n++) {
            opt = obj.options[n];
            // 4,5
            if ( opt.selected && (!opt.getAttribute('is_selected') || opt.getAttribute('is_selected') == 'no') ) {
                opt.setAttribute('is_selected', 'yes');
                selectedList.push(n);
                if (!ctrlKey) {    // if: 4.1; else: 5
                    var m = 0;
                    while (m < selectedList.length) {
                        opt = obj.options[ selectedList[m] ];
                        if (!opt.selected) {
                            opt.setAttribute('is_selected', 'no');
                            selectedList.splice(m, 1);
                            m--;
                        }
                        m++;
                    }
                }
                i = n; break;
            } else if (!opt.selected && opt.getAttribute('is_selected') == 'yes') {    // 4.2
                opt.setAttribute('is_selected', 'no');
            }
        }
    }
    var j = obj.options[i].text.indexOf(c);
    if (j > 0 || !obj.options[i].selected) return;
    // j - признак уровня, -1 или 0
    while (i++ < obj.options.length && (
            obj.options[i].text.indexOf(c) >= 0 && j == -1    // и не 1-й уровень
            ||
            obj.options[i].text.indexOf(c) >= 2 && j == 0    // или не 2-й уровень
    )) {
        obj.options[i].selected = true;
        obj.options[i].setAttribute('is_selected', 'yes');
        if (!selectedList.in_array(i)) {
            selectedList.push(i);
        }
    }
}
</script>

В SELECT-те должны стоять такие события:
Код
onChange='select(this)' onKeyDown='regKey(event)'

http://ciber-slash.hotbox.ru/stuff/Multi-select_status.htm

Добавлено @ 18:57 
Проверям, тестим на баги... smile 

Автор: Ciber SLasH 18.4.2006, 00:53
4-я версия:
[+] Теперь можно и снимать выделение с группы, нажав с удержанием <Ctrl> на подкатегории
Код
<script type='text/javascript'>
/*** Проверка вхождения элемента в массив ***/
Array.prototype.in_array = function (value) {
    for (var i = 0; i < this.length; i++)
        if (this[i] == value) return i;
return -1;
}
////////////////////////////////////////////////////////////////////////////////
var ctrlKey = 0;            // состояние клавиши <Ctrl>
var selectedList = [];        // список текущих выделенных пунктов
// В Опере последний символ имеет значение %20, а в других бродилках — %A0
var c = window.opera ? unescape('%B7%B7%20') : unescape('%B7%B7%A0');
////////////////////////////////////////////////////////////////////////////////
/*** Регистрирование клавиш ***/
function regKey(ev) {
var evt = ev || event;
    ctrlKey = evt.ctrlKey;
}
/*** Групповое выделение опций в списке выбора ***/
function select(obj) {
var i = obj.selectedIndex;
var pr = true;        // признак необходимости обработать 4 и 5 состояние
    if (i == -1) {    // если нет выделенных пунктов (сняли все ранее выделенные)
        selectedList.splice(0, 1);
        return;
    } else {
        var opt = obj.options[i];
    }
    if (selectedList.length == 0) {    // 1
        opt.setAttribute('is_selected', 'yes');
        selectedList.push(i);
        pr = false;
    } else if (selectedList.in_array(i) > -1) {    // 2,3
        var n = 0;
        while (n < selectedList.length) {
            opt = obj.options[ selectedList[n] ];
            if ( (selectedList[n] != i && !ctrlKey) || (ctrlKey && !opt.selected) ) {    // 2,3
                if (ctrlKey && !opt.selected) {    // 3
                    i = selectedList[n];
                }
                opt.setAttribute('is_selected', 'no');
                selectedList.splice(n, 1);
                n--;
                pr = false;
            }
            n++;
        }
    }
    if (pr) {    // 4,5
        for (var n = 0; n < obj.options.length; n++) {
            opt = obj.options[n];
            // 4,5
            if ( opt.selected && (!opt.getAttribute('is_selected') || opt.getAttribute('is_selected') == 'no') ) {
                opt.setAttribute('is_selected', 'yes');
                selectedList.push(n);
                if (!ctrlKey) {    // if: 4.1; else: 5
                    var m = 0;
                    while (m < selectedList.length) {
                        opt = obj.options[ selectedList[m] ];
                        if (!opt.selected) {
                            opt.setAttribute('is_selected', 'no');
                            selectedList.splice(m, 1);
                            m--;
                        }
                        m++;
                    }
                }
                i = n; break;
            } else if (!opt.selected && opt.getAttribute('is_selected') == 'yes') {    // 4.2
                opt.setAttribute('is_selected', 'no');
            }
        }
    }
    var j = obj.options[i].text.indexOf(c);
    if (j > 0) return;
    pr = obj.options[i].selected;    // признак добавления/снятия пунктов в группе
    // j - признак уровня, -1 или 0
    while (i++ < obj.options.length && (
            obj.options[i].text.indexOf(c) >= 0 && j == -1    // и не 1-й уровень
            ||
            obj.options[i].text.indexOf(c) >= 2 && j == 0    // или не 2-й уровень
    )) {
        obj.options[i].selected = pr;
        obj.options[i].setAttribute('is_selected', (pr ? 'yes' : 'no'));
        var pos = selectedList.in_array(i);
        if (pr) {
            if (selectedList.in_array(i) == -1) selectedList.push(i);
        } else {
            if (pos > -1) selectedList.splice(pos, 1);
        }
    }
}
</script>
 

Автор: 12345c 19.4.2006, 15:50
Не снимается (IE), кроме 1 строчки. И вернулась ошибка с групповым до-выделением, если кликнуть по группе вверху, но одиночное довыделение, если по группе внизу выделенной группы.

Может быть, хитро над оподключать? Я делал так:
Код

<select size="20" multiple onchange="select(this)">
<option value='all' selected="selected">&raquo; Все форумы</option><option value="c_1" class='cat'>Программирование</option>
 

Цитата
забыл ещё добавить onKeyDown='regKey(event)'

о, так работает исправно, IE,FF1.06 

Автор: Ciber SLasH 19.4.2006, 15:56
12345c, забыл ещё добавить onKeyDown='regKey(event)', т.е.
Код
<select size="20" multiple onchange="select(this)" onKeyDown='regKey(event)'>
 

Автор: Ciber SLasH 7.6.2006, 10:24
Комментарии и подробности по моей доработки в аттаче... 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)