Свой Select на чистом CSS и JavaScript

Свой Select на чистом CSS и JavaScript
Содержание:
  1. Зачем создавать свой Select, если есть <select>
  2. Подключение CSS и JavaScript файлов
  3. Вставка селекта на страницу и его активация
  4. Свойства и методы ItcCustomSelect
  5. Что внутри? Как написан ItcCustomSelect?
  6. Комментарии

В этой статье мы разберём пример реализации кастомного селекта на чистом CSS и JavaScript, который будет очень похож на стандартный HTML-тег <select>.

Зачем создавать свой Select, если есть <select>

Когда мы создаем селект с помощью стандартного HTML-элемента <select>, мы ограничены в его настройке. Так как не всегда можем стилизовать и запрограммировать его так, как нам это нужно.

В этом случае более грамотным решением будет написать свою реализацию селекта с нужной логикой и оформлением.

Здесь мы рассмотрим Select, расположенный на GitHub, который называется ItcCustomSelect. Он написан на чистом CSS и JavaScript. ItcCustomSelect имеет простой функционал, который позволяет выбрать одно из нескольких значений в выпадающем списке.

Вот так он выглядит:

Вид селекта, спроектированного на чистом CSS и JavaScript, в закрытом состоянии Вид селекта, спроектированного на чистом CSS и JavaScript, в открытом состоянии

Несмотря на то, что ItcCustomSelect имеет только базовый функционал, его в зависимости от задачи можно расширить. Вообще в веб-разработке создание выпадающего меню – это одна из наиболее часто возникающих задач.

Подключение CSS и JavaScript файлов

Исходные коды ItcCustomSelect расположены на GitHub в папке custom-select. ItcCustomSelect - это один из компонентов в наборе ui-components. Там кроме него есть ещё много других, которые вы можете использовать на сайте.

ItcCustomSelect состоит из 2 файлов: itc-custom-select.css и itc-custom-select.js. Скрипт написан на чистом JavaScript и не имеет зависимостей от других плагинов, в том числе и от библиотеки jQuery.

Для его установки необходимо загрузить в своё веб-приложение или сайт 2 этих файла и подключить их:

HTML
<!-- подключаем CSS-селекта -->
<link rel="stylesheet" href="/aasets/css/itc-custom-select.css">
<!-- подключаем JS-селекта -->
<script src="/assets/js/itc-custom-select.js"></script>

CSS подключаем с помощью тега <link>, а JavaScript с помощью <script>.

Вставка селекта на страницу и его активация

Кастомный селект представляет собой класс ItcCustomSelect. Работать с ним можно 2 способами.

Вариант 1. Он подразумевает непосредственную вставку HTML-кода селектора на страницу и его инициализацию с помощью JavaScript как ItcCustomSelect.

В этом варианте необходимо вставить на страницу полный HTML-код селекта, который имеет следующую структуру:

HTML
<!-- Изначально не активна ни одна опция -->
<div class="itc-select" id="select-1">
  <!-- Кнопка для открытия выпадающего списка -->
  <button type="button" class="itc-select__toggle" name="car" value="" data-select="toggle" data-index="-1">Выберите из списка</button>
  <!-- Выпадающий список -->
  <div class="itc-select__dropdown">
    <ul class="itc-select__options">
      <li class="itc-select__option" data-select="option" data-value="volkswagen" data-index="0">Volkswagen</li>
      <li class="itc-select__option" data-select="option" data-value="ford" data-index="1">Ford</li>
      <li class="itc-select__option" data-select="option" data-value="toyota" data-index="2">Toyota</li>
      <li class="itc-select__option" data-select="option" data-value="nissan" data-index="3">Nissan</li>
    </ul>
  </div>
</div>

После вставки HTML-кода его корневой элемент, то есть .itc-select, нужно активировать как ItcCustomSelect посредством JavaScript:

JavaScript
// select-1 – id элемента
const select1 = new ItcCustomSelect('#select-1');

Если нужно сформировать опции через AJAX, то сделать это можно очень просто, например, так:

HTML
<!-- Изначально не активна ни одна опция -->
<div class="itc-select">
  <!-- Кнопка для открытия выпадающего списка -->
  <button type="button" class="itc-select__toggle" name="car" value="" data-select="toggle" data-index="-1" disabled>Выберите из списка</button>
  <!-- Выпадающий список -->
  <div class="itc-select__dropdown">
    <ul class="itc-select__options"></ul>
  </div>
</div>

<script>
  (async() => {
    const response = await fetch('https://jsonplaceholder.typicode.com/users')
    if (response.ok) {
      const data = await response.json();
      const values = Object.keys(data).map((key, index) => {
        return `<li class="itc-select__option" data-select="option" data-value="${key}" data-index="${index}">${data[key].name}</li>`;
      });
      document.querySelector('.itc-select__options').innerHTML = values.join('');
      new ItcCustomSelect('#select-1');
      document.querySelector('.itc-select__toggle').disabled = false;
    }
  })();
</script>

Теперь более подробно про HTML-код. В нём имеется элемент <button>, который используется для переключения видимости выпадающего списка. Действие, которая эта кнопка выполняет, определяется в JavaScript через атрибут data-select="toggle".

У кнопки кроме data-select="toggle" имеются ещё другие атрибуты name и value. Они определяют соответственно имя селекта и его значение. Посредством них, если селект поместить в <form>, его значение вместе с другими данными формы будут отправлены на сервер.

Атрибут data-index содержит индекс выбранной опции. Если по умолчанию не должна быть активна какая-то опция, то value следует задать пустую строку, а data-index – значение -1.

Внутри itc-select__options необходимо расположить необходимые опции. Здесь это осуществляется посредством <li>, к которому необходимо добавить класс itc-select__option и следующие атрибуты:

  • data-select="option" – означает, что это опция;
  • data-value – значение опции;
  • data-index – индекс (порядковый номер) опции.

Если изначально какая-то опция должна быть активна, то к ней необходимо добавить класс itc-select__item_selected. Кроме этого для <button> в атрибут value нужно поместить её значение, в data-index – её индекс, а в содержимое – её контент.

Пример HTML-кода, в котором первая опция установлена по дефолту:

HTML
<!-- по дефолту активна 1 опция -->
<div class="itc-select" id="select-1">
  <button type="button" class="itc-select__toggle" name="car" value="ford" data-select="toggle" data-index="1">Выберите из списка</button>
  <div class="itc-select__dropdown">
    <ul class="itc-select__options">
      <li class="itc-select__option" data-select="option" data-value="volkswagen" data-index="0">Volkswagen</li>
      <li class="itc-select__option select__option_selected" data-select="option" data-value="ford" data-index="1">Ford</li>
      <li class="itc-select__option" data-select="option" data-value="toyota" data-index="2">Toyota</li>
      <li class="itc-select__option" data-select="option" data-value="nissan" data-index="3">Nissan</li>
    </ul>
  </div>
</div>

Вариант 2. Этот вариант предполагает вставку в HTML пустого элемента <div>, который следует использовать в качестве селекта. То есть в этом случае HTML-код будет следующим:

HTML
<div id="select-2"></div>

В этом варианте всю структуру создаст сам JavaScript. Варианты и дефолтный текст селекту необходимо передать при создании экземпляра класса ItcCustomSelect посредством аргумента в формате объекта:

JavaScript
const select2 = new ItcCustomSelect('#select-2', {
  name: 'car', // значение атрибута name у кнопки
  targetValue: 'ford', // значение по умолчанию
  options: [['volkswagen', 'Volkswagen'], ['ford', 'Ford'], ['toyota', 'Toyota'], ['nissan', 'Nissan']], // опции
});

Значение options – это массив массивов. Первый элемент массива – это значение опции, а второй – её текстовое представление.

Если не нужно чтобы селект имел значение по умолчанию, то установите ключу targetValue пустую строку или вообще его не указывайте:

JavaScript
const select2 = new ItcCustomSelect('#select-2', {
  name: 'car', // значение атрибута name у кнопки
  options: [['volkswagen', 'Volkswagen'], ['ford', 'Ford'], ['toyota', 'Toyota'], ['nissan', 'Nissan']], // опции
});

Свойства и методы ItcCustomSelect

После инициализации селекта, нам будут доступны следующие свойства и методы:

  • value – позволяет как получить выбранную опцию, так и установить её;
  • selectedIndex – индекс выбранного элемента (нумерация начинается с 0);
  • show() – показывает выпадающий список с опциями;
  • hide() – скрывает dropdown меню;
  • toggle() – переключает видимость выпадающего меню;
  • dispose() – удаляет обработчики событий, связанных с этим селектом.

Использование свойства value:

JavaScript
// установим в качестве выбранной опции элемент со значением toyota
select2.value = 'toyota';
// получим значение выбранной опции
console.log(select2.value); // toyota

Кроме этого value позволяет также сбросить выбранную опцию. Для этого value нужно установить пустую строку или значение не соответствующее ни одной из опций:

JavaScript
// сбросим выбранную опцию
select2.value = '';

Использование свойства selectedIndex:

JavaScript
// установим в качестве выбранной опции элемент с индексом 2
select2.selectedIndex = 2;
// получим индекс выбранной опции
console.log( select2.selectedIndex );

Если ни один из элементов не выбран, то selectedIndex возвращает -1:

JavaScript
select2.value = '';
// получим индекс выбранного элемента
console.log( select2.selectedIndex ); // -1

Сбросить выбранный элемент можно не только посредством value, но также, если установить selectedIndex число -1 или индекс элемента, которого нет:

JavaScript
select2.selectedIndex = -1;

Если нам необходимо выполнить некоторые действия при выборе элемента отличного от текущего, то мы можем воспользоваться событием itc.select.change, генерируемым в JavaScript коде:

JavaScript
document.querySelector('#select-2').addEventListener('itc.select.change', (e) => {
  const btn = e.target.querySelector('.itc-select__toggle');
  // выбранное значение
  console.log(`Выбранное значение: ${btn.value}`);
    // индекс выбранной опции
  console.log(`Индекс выбранной опции: ${btn.dataset.index}`);
  // выбранный текст опции
  const selected = e.target.querySelector('.itc-select__option_selected');
  const text = selected ? selected.textContent : '';
  console.log(`Выбранный текст опции: ${text}`);
});

Кроме как использовать событие, это действие также можно выполнить с помощью метода onSelected при создании экземпляра объекта ItcCustomSelect:

2 способ (через метод onSelect):

JavaScript
new ItcCustomSelect('#select-2', {
  name: 'car',
  targetValue: 'ford',
  data: [['volkswagen', 'Volkswagen'], ['ford', 'Ford'], ['toyota', 'Toyota'], ['nissan', 'Nissan']],
  onSelected(select, option) {
    // выбранное значение
    console.log(`Выбранное значение: ${itc.select.value}`);
    // индекс выбранной опции
    console.log(`Индекс выбранной опции: ${select.selectedIndex}`);
    // выбранный текст опции
    const text = option ? option.textContent : '';
    console.log(`Выбранный текст опции: ${text}`);
  }
});

Что внутри? Как написан ItcCustomSelect?

Компонент Select построен с использованием HTML, CSS и JavaScript. Его HTML-код мы уже рассматривали. Он имеет следующую структуру:

HTML
<div class="itc-select">
  <button type="button" class="itc-select__toggle" name="car" value="" data-select="toggle" data-index="-1">Выберите из списка</button>
  <div class="itc-select__dropdown">
    <ul class="itc-select__options">
      <li class="itc-select__option" data-select="option" data-value="volkswagen" data-index="0">Volkswagen</li>
      <li class="itc-select__option elect__option_selected" data-select="option" data-value="ford" data-index="1">Ford</li>
      <li class="itc-select__option" data-select="option" data-value="toyota" data-index="2">Toyota</li>
    </ul>
  </div>
</div>

Элемент с классом itc-select является корневым. В нём находится вся HTML-структура селекта. Тег <button> с классом itc-select__toggle предназначен для отображения выбранного значения опции и переключения видимости выпадающего списка. Само выпадающее меню реализовано через элемент .itc-select__dropdown. Оно с помощью CSS настраивается так, чтобы оно было расположено под <button>. Список вариантов .itc-select__options организован посредством маркированного списка. Выбранный элемент в нём отмечается посредством добавления к нему класса itc-select__option_selected.

Этот HTML-кода не обязательно создавать вручную на странице. Имеется также автоматический вариант его создания с помощью JavaScript. В этом случае как уже было отмечено выше нужно лишь на страницу поместить пустой элемент <div>.

Затем необходимо активировать его с помощью JavaScript как ItcCustomSelect. На этом с HTML-кодом всё.

Классы будем использовать в CSS для добавления к элементам стилей, а data-атрибуты – в JavaScript.

CSS-код компонента Select можно посмотреть на GitHub.

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

CSS
.itc-select {
  position: relative;
  ...
}

Элемент .itc-select__toggle стилизуем как кнопку. Текущий вариант селекта будем выводить как её содержимое:

CSS
.itc-select__toggle {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  padding: 0.375rem 0.75rem;
  font-size: 1rem;
  font-style: italic;
  line-height: 1.4;
  background-color: #fff;
  border: 1px solid #ccc;
  border-radius: 0.3125rem;
  cursor: pointer;
  user-select: none;
}

Иконку к кнопке добавим через псевдоэлемент ::after:

CSS
.itc-select__toggle::after {
  flex-shrink: 0;
  width: 0.75rem;
  height: 0.75rem;
  margin-left: 1rem;
  background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" height="100" width="100"%3E%3Cpath d="M97.625 25.3l-4.813-4.89c-1.668-1.606-3.616-2.41-5.84-2.41-2.27 0-4.194.804-5.777 2.41L50 52.087 18.806 20.412C17.223 18.805 15.298 18 13.03 18c-2.225 0-4.172.804-5.84 2.41l-4.75 4.89C.813 26.95 0 28.927 0 31.23c0 2.346.814 4.301 2.439 5.865l41.784 42.428C45.764 81.174 47.689 82 50 82c2.268 0 4.215-.826 5.84-2.476l41.784-42.428c1.584-1.608 2.376-3.563 2.376-5.865 0-2.26-.792-4.236-2.375-5.932z"/%3E%3C/svg%3E');
  background-size: cover;
  content: "";
}

По умолчанию dropdown меню не будет показываться. Включение его отображения будем осуществлять посредством добавления к нему класса itc-select_show:

CSS
.itc-select_show .itc-select__dropdown {
  display: block;
}

При этом при показе dropdown меню иконку будем поворачивать на 180 градусов посредством CSS-трансформации:

CSS
.itc-select_show .itc-select__toggle::after {
  transform: rotate(180deg);
}

CSS-код для стилизации dropdown меню:

CSS
.itc-select__dropdown {
  position: absolute;
  top: 2.5rem;
  right: 0;
  left: 0;
  z-index: 2;
  display: none;
  max-height: 10rem;
  overflow-y: auto;
  background-color: #fff;
  border: 1px solid #ccc;
  border-radius: 0.3125rem;
}
.itc-select__options {
  margin: 0;
  padding: 0;
  list-style: none;
}
.itc-select__option {
  padding: 0.375rem 0.75rem;
}

Стилизация при наведении на пункт меню:

CSS
.itc-select__option:hover {
  background-color: #f5f5f5;
  cursor: pointer;
  transition: 0.2s background-color ease-in-out;
}

JavaScript код компонента доступен на GitHub. Его можно открыть, используя эту ссылку.

Код написан в виде класса ItcCustomSelect:

JavaScript
class ItcCustomSelect {
  ...
}

Свойства и методы, которые не нужно использовать вне класса, начинаются с нижнего подчеркивания:

JavaScript
class ItcCustomSelect {
  constructor(target, params) {
    this._el = typeof target === 'string' ? document.querySelector(target) : target;
    ...
  }
  ...
  _onClick(e) { ... }
  ...
}

Подчеркивание перед именем — это общепринятое соглашение для именования свойств и методов, к которым не нужно обращаться вне класса. Они используются для реализации логики внутри класса.

Структура JavaScript кода:

class ItcCustomSelect {
  // статический метод для создания HTML-кода селекта
  static template(params) { }
  // для закрытия открытого селекта при клике вне его
  static hideOpenSelect() { }
  // конструктор
  constructor(target, params) { }
  // обработчик события click
  _onClick(e) { }
  // обновляет значения атрибутов в зависимости от выбранной опции, генерирует событие 'itc.elect.change'
  _updateOption(el) { }
  // сбрасывает состояние, генерирует событие 'itc.select.change'
  _reset() { }
  // при изменении выбранной опции
  _changeValue(option) { }
  // включает отображение выпадающего списка
  show() { }
  // скрывает список с опциями
  hide() { }
  // переключает список с опциями
  toggle() { }
  // удаления слушателей события click селекта
  dispose() { }
  // геттер, который возвращает значение выбранной опции
  get value() { }
  // сеттер, который позволяет установить опцию по значению
  set value(value) { }
  // геттер, который возвращает индекс выбранной опции
  get selectedIndex() { }
  // сеттер, который позволяет выбрать опцию по её индексу
  set selectedIndex(index) { }
}

ItcCustomSelect.hideOpenSelect();

Комментарии: 50

Лютикова Полина
Лютикова Полина

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

kliuiko
kliuiko

Замените функцию show на эту

show() {
        document.querySelectorAll(`.${this.constructor.EL_SHOW}`)
            .forEach((el) => {
                el.classList.remove(this.constructor.EL_SHOW);
            });
        this._el.classList.add(`${this.constructor.EL_SHOW}`);
    }
FuyuYoru
FuyuYoru

Здравствуйте, Александр. крутой селект. такой вопрос, данный плагин поддерживает возможность создания вложенных селектов?

Александр Мальцев
Александр Мальцев

Привет! Спасибо! Что значит вложенные? Или всё таки связанные между собой? Есть какой-то пример, чтобы понять что нужно?

FuyuYoru
FuyuYoru

Я имел ввиду, что внутри одного раскрывающегося селекта лежат ещё несколько таких же селектов со своими опциями, но этот вопрос отпал :) Начал использовать сеттер selectedIndex, и значение на селекте никак не хотело переключаться на нужный индекс, переключалось на "-1". Правильно ли я понял, что это из-за вызова метода _reset?

Александр Мальцев
Александр Мальцев

Да, этот метод сбрасывает состояние селекта. Теперь не одна из опций не будет выбрана.

igor_khod
igor_khod

Здравия, Александр!

Это - лучший селект из всех, с которыми я работал. Он оказался даже лучше, чем я ожидал! Особенно в плане использования в адаптивном сайте. Работает абсолютно корректно при любой ширине экрана и любой длине строк в выбираемых значениях! Респект и уважуха!

Я селект использую в форме обратной связи, вызываемой в модальном окне. Отсюда приходится загружать функции из файла "itc-custom-select.js" после того как будет выстроена сама форма в окне. Получается, что эти же функции загружаются при каждом вызове формы. Этих загрузок может быть несколько.

Вопрос - стоит ли беспокоится об этих повторных загрузках или лучше заморочиться на тему что бы не было повторных загрузок? Я использую ваши модальные окна.
Александр Мальцев
Александр Мальцев

Привет! Благодарю!

Если правильно понял, то нужно просто удалить ненужные данные из памяти. В движке JavaScript этим занимается фоновый процесс «сборщик мусора».

Для этого, когда экземпляр класса ItcCustomSelect не будет нужен, необходимо удалить обработчики события и ссылку на этот объект:

// создаём экземпляр класса ItcCustomSelect
let select = new ItcCustomSelect('.select');

// когда не нужен этот элемент
// удаляем обработчики события, связанные с ним
select.dispose();
// присваиваем переменной select, например, значение null
select = null;

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

Egor80
Egor80

Спасибо, крутой Селект! Использую дефолтный селект-1 только со своим оформлением. Из-за слабого знания js не могу понять как сделать активным выбор селекта передав ему Значение из ajax. Идея сделать активным выбор получив Значение из ajax (значение после обработки формы). Буду Вам признателен, но в любом случае спасибо)

Egor80
Egor80

Задача решена, спасибо.

Александр Мальцев
Александр Мальцев

Спасибо за отзыв! Пример с AJAX добавил в статью может кому-нибудь пригодится.

ViktarStasela
ViktarStasela
Добрый день, Александр. Использовал ваш кастомный селект в проекте. В проекте несколько страниц, и на разных страницах используются разные селеткты. Все они инициализируются в одном файле и все замечательно работает. Но есть один нюанс. Заключается он в том, что если на страница отсутсвует разметка под селект (он просто не предусмотрен на этой странице), но он инициализируется для другой страницы, он вступает в конфликт с swiper (https://swiperjs.com/) и отключает его. Если в разметке страницы присутствуют все селекты, которые инициализированы в js, то swiper работает замечательно. Подскажите, с чем это может быть связано и как решить?
Спасибо!
ViktarStasela
ViktarStasela
Даже отключает не только swiper, но и все сторонние бандлы.
Александр Мальцев
Александр Мальцев
Привет, посмотрю в ближайшее время.
Александр Мальцев
Александр Мальцев
1. Можно перед тем, как инициализировать элемент как селект проверить, а существует ли этот элемент на странице:
const el = document.querySelector('#select-1');
if (el) {
  new ItcCustomSelect(el);
}
2. На GitHub добавил статический метод create. Он проверяет наличие элемента на странице, и если он имеется, то активирует его как ItcCustomSelect:
ItcCustomSelect.create('#select-1');
Также немного обновил код селекта на Github и текст статьи. В рамках ребрединга переименовал его в ItcCustomSelect.
ViktarStasela
ViktarStasela
Спасибо!
cushhy
cushhy
Доброго времени, спасибо за данный селект, но я начинающий, и вообще не разбираюсь в JS. Прошу помогите мне) У меня на сайте есть калькулятор, состоит он из ползунка, который дает выбрать площадь( сайт по натяжке потолков ), далее идет три селекта, материал, количество люстр, и количество ламп. Вот мне нужно сделать калькулятор, чтобы при выборе опции из селекта, стоимость сразу шла в общую и суммировалась( а еще нужно чтобы площадь умножалась на стоимость материала ), как это сделать я понимаю на словах, но отобразить в коде не могу(. Я буду примерно благодарен, если поможете, заранее спасибо))) Если потребуется код, прислать смогу.
agat
agat
Здравствуйте, Александр! Очень классная статья! Спасибо)
Подскажите, как добавить обработчик события по клику на выбираемый элемент выпадающего списка?

Моя задача — сделать так, чтобы при клике на выбранный элемент выпадающего списка открывался соответствующий таб. Функцию открытия соответствующего таба я написал, а вот как теперь привязать элемент списка к табу… пока не знаю…
Александр Мальцев
Александр Мальцев
Здравствуйте! Спасибо!
Для этого можно использовать событие select.change:
document.querySelector('.select').addEventListener('select.change', (e) => {
  // ...
}
agat
agat
Спасибо! Буду пробовать)
Heitz
Heitz
Сколько же соплей летело по стенам, когда я месяц делал свой первый кастомный селект )… потом загуглил и нащёл это ) и вся боль в том, что он похож на мой практически идеально, вплодь до названия css классов…
Myshynov
Myshynov
Здраствуйте Александр! Спасибо вам за проделанную работу. Отличная реализация кастомных селектов!

У меня возникла небольшая проблема.
Дело в том что в моём списке используются стили и обвертки в «span». Если назначить одну из опций по умолчанию то всё ОК и спаны с классами и стилями выводятся в кнопку. Но как только выбираешь из списка другую опцию в кнопке отображается строка без «span».
Вот пример кода чтобы вам было проще меня понять

const registrationSelect = new CustomSelect('#registration-select', {
  name: 'registration', // значение атрибута name у кнопки
  targetValue: 'RegularRegistration', // значение по умолчанию
  options: [
    ['RegularRegistration', 'Regular Registration <span class="price">($35.00)</span> <span class="sub-name">Breakfast pastries and refreshments included</span>'],
    ['RegularRegistration2', '2Regular Registration <span class="price">($35.00)</span> <span class="sub-name">Breakfast pastries and refreshments included</span>'],
    ['RegularRegistration3', '3Regular Registration <span class="price">($35.00)</span> <span class="sub-name">Breakfast pastries and refreshments included</span>'],
    ['RegularRegistration4', '4Regular Registration <span class="price">($35.00)</span> <span class="sub-name">Breakfast pastries and refreshments included</span>']
  ], // опции
});
Myshynov
Myshynov
Я 2 дня не находил себе места перечитывал статью и все комментарии.
И как только я написал вопрос я нашел путь к ответу. Если вдруг кто-то столкнется с таким же вопросом то вот решение. Найди верхнюю строку и замени в ней .textContent на .innerHTML:
this._elToggle.textContent = option.textContent;
this._elToggle.innerHTML = option.innerHTML;
Только заметил что в самом вопросе не отобразились «span». они у меня были в значении «options»
Александр Мальцев
Александр Мальцев
Привет! Спасибо за отзыв! Рад, что кастомный селект понравился.
Необходимо добавить ещё в начало метода _update следующую строчку (на Github обновил этот момент):
_update(option) {
  option = option.closest('.select__option'); // добавить эту строчку
  // ...
}
Юрий
Юрий
Здравствуйте, спасибо за плагин!!!
Столкнулся с несколькими проблемами.
Проект разрабатываю на Laravel.
Первая проблема — при отправке данных на бэк, данные селектов не приходят, несмотря на наличие атрибута name и значения, но эту проблему обошел путем добавления hidden input и дальнейшего добавления в него выбранных данных при помощи js.
Вторая проблема — есть форма с двумя селектами, которая заполняется пользователем поэтапно. Есть кнопки prev и next, при нажатии next блок с первым селектом (data-block=«1») скрывается и ajax-запросом получаются подкатегории выбранной в первом блоке категории, и на основании ответа во втором блоке (data-block=«2») создается новый селект с подкатегориями, после чего отображается второй блок.

html код

<div class="form-group" data-block="1">
                        <div class="select" id="category-select">
                            <button type="button" class="select__toggle" id="category_id-btn" name="category_id-btn" value=""
                                    data-select="toggle" data-index="-1">
                                Выберите категорию
                            </button>
                            <div class="select__dropdown">
                                <ul class="select__options">
                                    @php $index = 0; @endphp
                                    @foreach($categories as $category)
                                        <li class="select__option" data-select="option" data-value="{{ $category->id }}"
                                            data-index="{{ $index++ }}">{{ $category->name }}</li>
                                    @endforeach
                                </ul>
                            </div>
                        </div>
                        <input type="hidden" name="category_id" id="category_id">
                    </div>
                    <div class="form-group" data-block="2">
                        <div class="select" id="service-select"></div>
                        <input type="hidden" name="service_id" id="service_id">
                    </div>


js код

let serviceSelect = new CustomSelect('#service-select', {
                                    name: 'service_id-btn',
                                    options
                                });
Суть проблемы — при первом отображении второго селекта все работает, но когда нажимаю кнопку prev, скрываю второй селект, перехожу на первый, выбираю новую категорию и перехожу обратно на второй селект, html код с новыми подкатегориями формируется, НО при клике на селект не открывается дропдаун с подкатегориями, если еще раз вернусь назад, выбираю другую категорию, и перехожу снова на второй селект, дропдаун уже открывается, но при выборе нужной подкатегории он уже не закрывается.
Александр Мальцев
Александр Мальцев
Привет!
Вот пример состоящий из 2 селектов. Может он поможет. В нём при выборе элемента из 1 селекта, второй наполняется данными посредством AJAX в зависимости от значения первого селекта.
Gregory
Gregory
1 — поставьте пожалуйста кнопку для комментария в начале списка комментариев, а то пригодится листать в самый низ списка комментов
2 — в начале скрипта вы определяете константы вне класса, вне конструктора. Это для каких целей делается?
Gregory
Gregory
Небольшая модификация CSS для мобильных — может пригодится

@media (max-width: 500px) {
  .select__options{
    position: fixed;
    height: 100%;
    overflow-y: scroll;
    top: 0;
    left: 0;
    background: white;
    width: 100vw;
    padding: 20px;
  }
}
Gregory
Gregory
да вот ещё

* {
      box-sizing: border-box;
}
Александр
Александр
Александр, добрый день. Имеется ли возможность задавать разные значения переменной
selectedContent
для разных селектов. По умолчанию стоит 'Выберите из списка';
Daniel
Daniel
Здраствуйте Александр. Большое спасибо за разработанный плагин CustomSelect, отличная работа. сэкономлено кучу времени. У меня возник один вопрос: на базе вашего плагина пишу калькулятор и столкнулся с небольшой проблемой, а именно: создаю функцию Reset для сброса всех значений «name», «value» в дефолтное состояние но при клике на кнопке reset не получаю никакого результата.
Вот пример кода:
function resetBtn() {
  let btnReset = document.queryselectorAll("select-reset");
  btnReset.value = "" ;
  btnRest. name = "" ;
};
<button type="reset" onClick="resetBtn()">Reset</button>
Буду очень благодарен за помощь. Спасибо.
Александр Мальцев
Александр Мальцев
Добрый день! Наверно так:
function resetBtn() {
  let btnReset = document.querySelectorAll('.select-reset');
  btnReset.forEach((element) => {
    element.value = '';
    element.name = '';
  });
}
Не знаю, что такое select-reset? Класс?
После того как выбрали элементы с помощью querySelectorAll, их нужно перебрать, например, с помощью forEach. Так напрямую нельзя.
Daniel
Daniel
Большое спасибо за столь быстрый ответ. да, select-reset это я добавил некий клас, для сброса значений и в калькуляторе я использую несколько CustomSelect и радио-кнопки checkbox. ok, попробую с forEach. Ещё раз спасибо. Хорошего дня
Daria
Daria
Здравствуйте, Александр, спасибо вам за проделанную работу, ваш селект прекрасен))

у меня возникла проблема,
Мне нужно два селекта, они расположены рядом в форме, добавляю их Первым способом
все работает хорошо, кроме закрытия по клику на второй селект.

1) Кликаем на первый селект, ничего, не выбрано, кликаем, на второй селект, первый остается в открытом состоянии, но не активен при ховере, если кликнуть «оживает», получается два открытых одновременно селекта.

При этом в обратном порядке работает правильно
2) Кликаем второй селект, кликаем на первый второй закрыватся при клике на первый.

Александр Мальцев
Александр Мальцев
Привет! Спасибо за отзыв. Переписал код компонента, а также исправил этот баг. Также изменил название классов и атрибутов, поэтому обратите на это внимание при обновлении кода.
Кирилл
Кирилл
Верно ли понимаю, что мы можем управлять выбором значения по индексу или названию(value), так:
select1.selectedItem(6); // (где 6 — это 7 элемент списка).
Но у меня к сожалению, так не сработало :(

P.S. спасибо большое за реализацию.
Александр Мальцев
Александр Мальцев
Нужно передавать в формате объекта:
// по индексу
select1.selectedItem({index: 0})
// по значению
select1.selectedItem({value: 'Nissan'})
Кирилл
Кирилл
Благодарю!
Konstantin
Konstantin
Может быть это у меня что то с ip, но ни одна ссылка на материалы из этого поста не открывается. Выдает 404 ошибку. в том числе на GIT!!!
Александр Мальцев
Александр Мальцев
Поправил ссылки.
Александр
Александр
Александр, здравствуйте! Спасибо, за отличный вариант и достойную альтернативу дефолтному селекту. Пытаюсь это решение использовать в сортировке mFilter2, не получается всё сделать по уму да и вообще мало что получается честно говоря, делаю так:

<div class="select" id="mse2_sort">
  <div class="select__backdrop" data-select="backdrop"></div>
  
 <button type="button" class="select__trigger" data-select="trigger" value="asc" data-sort="resource|pagetitle" data-icon="fa-list">
    По умолчанию
 </button>

<div class="select__dropdown">
    <ul class="select__items">
      <li class="select__item" data-select="item"  value="asc" data-sort="extendresource|date"><i class="fa fa-play"></i>Дата</li>
      <li class="select__item" data-select="item" value="desc" data-sort="extendresource|size"><i class="fa fa-table"></i>Размер</li>
    </ul>
  </div>
</div>
<script>
 const select1 = new CustomSelect('#mse2_sort'); 
</script>
как я понимаю, так работать не будет, так как в файле default.js в параметрах скрипта mFilter2, завязано всё на теге «a»
sort_link: '#mse2_sort a'
если изменить на тег «li», то сортировка начинает работать, но не сохраняет выбранное значение при перезагрузке страницы. Александр, если возможно расскажите пожалуйста как нужно и лучше сделать, чтобы сортировка работала и сохраняла выбранное значение при перезагрузки страницы?
Александр Мальцев
Александр Мальцев
Привет! Спасибо за отзыв.
Чтобы в mFilter2 изменить sort_link можно использовать параметр &filterOptions:
[[!mFilter2? 
  ...
  &filterOptions=`{"sort_link": "#mse2_sort li"}`
]]
Установить выбранную сортировку в CustomSelect после перезагрузки страницы можно посредством добавления следующего скрипта на страницу:
<script>
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const sort = urlParams.get('sort');
let value = '';
if (sort) {
  const delim = sort.indexOf(':');
  const res = delim === -1 ? sort : sort.substring(0, delim);
  const $li = document.querySelector(`#mse2_sort li[data-sort="${res}"]`);
  if ($li) {
    value = $li.textContent;
  }
}
const select1 = new CustomSelect('#mse2_sort');
if (value) {
  select1.selectedItem({value: value})
} 
</script>
Александр
Александр
Александр, огромное спасибо за решение, в очередной раз выручили.
Владимир
Владимир
Александр, еще момент.

Александр Мальцев
Александр Мальцев
Спасибо! Этот момент тоже доработал и обновил код js-файла на Github.
Владимир
Владимир
Еще момент.
Если создание нового экземпляра объекта CustomSelect происходит таким образом
const select1 = new CustomSelect(".select");
то в консоли получаю следующую ошибку
Александр Мальцев
Александр Мальцев
Спасибо! Поправил код скрипта на Github.
Владимир
Владимир
Добрый день!

На странице категории есть сортировка, сделанная через стандартный select. При выборе какой-либо сортировки происходит перезагрузка страницы.

Пытаюсь применить Ваш плагин. В итоге код получился такой
<div class="select">
  <div class="select__backdrop" data-select="backdrop"></div>
  <button type="button" class="select__trigger" data-select="trigger">По умолчанию</button>
  <div class="select__dropdown">
    <ul class="select__items">
      <?php foreach ($sorts as $sorts) { ?>
      <?php if ($sorts['value'] == $sort . '-' . $order) { ?>
      <li class="select__item select__item_selected" data-select="item" data-url="<?php echo $sorts['href']; ?>"><?php echo $sorts['text']; ?></li>
      <?php } else { ?>
      <li class="select__item" data-select="item" data-url="<?php echo $sorts['href']; ?>"><?php echo $sorts['text']; ?></li>
      <?php } ?>
      <?php } ?>
    </ul>
  </div>
</div>
<script>
const select1 = new CustomSelect(".select", { });

document.querySelector(".select").addEventListener("select.change", (e) => {
  location = document.querySelector(".select__item_selected").getAttribute("data-url"); 
});
</script>
Не получается подставить выбранное значение в button.
Подскажите, пожалуйста, как это можно сделать?
Александр Мальцев
Александр Мальцев
Здравствуйте!
Установите выбранное значение в button с помощью JavaScript:
document.querySelector('.select__trigger').textContent = document.querySelector('.select__item_selected').textContent;
Владимир
Владимир
Оказалось все так просто.
Спасибо, Александр!