Category: Frontend

18
Окт
2021

Авторизация React redux-form

Подскажите пожалуйста, как можно реализовать проверку наличия пользователя и вход в аккаунт после прохождения регистрации? Формы регистрации и авторизации подвязаны на redux-form.
const renderField = ({ input, label, type, meta: { touched,…

12
Окт
2021

Переход (в новой вкладке) на разные страницы при выборе карточки и нажатии кнопки

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

12
Окт
2021

Как оптимизировать длинную страницу сайта?

У меня есть сайт-таблица, где есть поле для подгрузки данных, и в этой таблице после отрисовки более 1000 строк, страница начинает лагать, и требуется заметное время для отрисовки. Как оптимизировать такую таблицу? что бы при подгрузке не …

21
Сен
2021

Здравствуйте, подскажите, пожалуйста, как можно модифицировать данный график

Есть данный код, получается такой график (синего цвета), мне бы хотелось немного его улучшить, а именно довести до такого до графика (красного цвета), а также добавить значения(точки), где график касается ось абсцисс.
const canvasPlot = do…

17
Сен
2021

Древовидная таблица HTML + CSS + JS

Всем Добрый вечер! Прошу помощи с вёрсткой. Хочу сделать древовидную таблицу, (данные могут меняться) но у меня постоянно слезает стиль в право. Имею такой вариант https://jsfiddle.net/nafanya123/L9txe6bq/
<body>
<button oncli…

14
Сен
2021

webpack. 1 ERROR in child compilations

Выдает ошибку при попытке build.
1 ERROR in child compilations (Use ‘stats.children: true’ resp. ‘–stats-children’ for more details)
const Path = require(‘path’);
const Webpack = require(‘webpack’);
const path = require(‘path’);
const { C…

14
Сен
2021

webpack. 1 ERROR in child compilations

Выдает ошибку при попытке build.
1 ERROR in child compilations (Use ‘stats.children: true’ resp. ‘–stats-children’ for more details)
const Path = require(‘path’);
const Webpack = require(‘webpack’);
const path = require(‘path’);
const { C…

12
Сен
2021

Nuxt Ошибка в запуска после переноса проекта на другой пк

Ошибка билда, неправильно собирает. В исходном коде все в порядке. До этого работал с проектом на страром маке, перенес проект на новый и он перестал билдить правильно
package.json
{
"name": "back",
"version&qu…

10
Сен
2021

Как подружить gulp-browser-sync и PhpStorm

Каким образом связать Gulp-browser-sync и PhpStorm. С файлами .html проблем нет, а вот с .php плагин не может получить доступ. При настройке browser-sync в gulpfile.js люди указывают proxy. Но я никак не могу понять откуда его взять в PS

10
Сен
2021

Как убедить начальство избавиться от Internet Explorer на фронтенде, или как с этим жить? [закрыт]

Собеседование фронтендера:
– Здравствуйте, а у вас нужно писать код под Internet Explorer?
– Да.
– Досвидания.

Проблема в том, что IE у нас входит в список поддерживаемых браузеров, и это очень больно.
Вопрос: Как убедить начальство прек…

05
Сен
2021

Как использовать webp, а для safari jpeg2000

Каким образом можно сделать так, чтобы по дефолту у меня использовалась картинка в формате webp, а для safari подставлялась jpeg2000, хотел сделать через тег picture и srcset, но не знаю, как сделать так, чтобы код понимал, какой браузер и…

03
Сен
2021

☕ Доступный автокомплит с нуля на JavaScript

Руководство по созданию компонента автодополнения с учетом всех требований доступности.

Эта статья – отрывок из книги Адама Сильвера Form Design Patterns из третьей главы «Формы бронирования авиабилетов», в которой рассматриваются способы, позволяющие пользователю указать страну назначения.

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

Важное предупреждение: это один из самых сложных UI-компонентов, с которыми вам когда-либо приходилось сталкиваться. Он намного сложнее, чем кажется.

Контрол автодополнения отображает список с тремя предложениями стран. Вторая опция выделена.
Контрол автодополнения отображает список с тремя предложениями стран. Вторая опция выделена.

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

Базовая разметка

Чтобы элемент работал при отключенном JavaScript, следует начать с нативных HTML-контролов.

Радио-кнопки не подходят, потому что в нашем списке слишком много опций. Search box [разбирается в книге ранее] медленный и может выдавать нулевой результат. Datalist слишком забагованный. В общем, вариантов не остается, придется воспользоваться обычным селектом.

Базовая разметка контрола автодополнения с использованием нативного элемента select
        <div class="field">
  <label for="destination">
    <span class="field-label">Destination</span>
  </label>
  <select name="destination" id="destination">
    <option value="">Select</option>
    <option value="1">France</option>
    <option value="2">Germany</option>
    <!-- … -->
  </select>
</div>
    

Улучшенная разметка

Если же JavaScript доступен, мы можем воспользоваться конструктором Autocomplete() [который будет написан позже], чтобы сгенерировать более продвинутую верстку.

Улучшенная разметка контрола автодополнения при доступном JavaScript
        <div class="field">
  <label for="destination">
    <span class="field-label">Destination</span>
  </label>
  <select name="destination" aria-hidden="true" tabindex="-1" class="visually-hidden">
    <!-- здесь опции -->
  </select>
  <div class="autocomplete">
    <input aria-owns="autocomplete-options--destination" autocapitalize="none" type="text" autocomplete="off"  aria-autocomplete="list" role="combobox" id="destination" aria-expanded="false">
    <svg focusable="false" version="1.1" xmlns="http://www.w3.org/2000/svg">
      <!-- контент SVG -->
    </svg>
    <ul id="autocomplete-options--destination" role="listbox" class="hidden">
      <li role="option" tabindex="-1" aria-selected="false" data-option-value="1" id="autocomplete_1">
    	  France
      </li>
      <li role="option" tabindex="-1" aria-selected="true" data-option-value="2" id="autocomplete_2">
    	  Germany
      </li>
      <!-- остальные опции -->
    </ul>
    <div aria-live="polite" role="status" class="visually-hidden">
  	13 results available.
    </div>
  </div>
</div>
    

Прячем селект без нарушения его доступности

Чтобы спрятать элемент seleсt, не сломав возможность отправки его значения на сервер [при отправке формы], нужно сделать следующее:

  1. Добавить класс visually-hidden, чтобы спрятать элемент от пользователей. Подробнее о паттерне visually-hidden
  2. Добавить атрибут aria-hidden="true", чтобы спрятать его от скринридеров.
  3. Добавить атрибут tabindex="-1", чтобы на нем нельзя было сфокусироваться с клавиатуры.

Такое скрытие намного лучше, чем использование display: none. Оно дает такой же эффект, но при этом не препятствует отправке значения селекта на сервер. Хотя пользователь не будет взаимодействовать напрямую с элементом select, так как мы его спрятали, его значение все еще необходимо отправлять на сервер для обработки, важно помнить об этом.

Перепривязка метки поля

Мы переместили атрибут id c select на input, чтобы связать метку label с текстовым полем. Это необходимо для скринридеров, а также увеличивает область взаимодействия (hit area).

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

Атрибуты текстового поля

  • Атрибут role="combobox" определяет тип поля ввода. Combo box – это “редактируемый контрол, связанный со списком предопределенных вариантов ввода“.
  • Атрибут aria-autocomplete="list" сообщает пользователям, что будет доступен список опций.
  • Атрибут aria-expanded описывает текущее состояние этого списка – свернутое (false) или развернутое (true).
  • Атрибут autocomplete="off" запрещает браузеру добавлять свои собственные предложения, которые будут мешать работе компонента.
  • И наконец, атрибут autocapitalize="none" не позволяет автоматически превращать первую букву в заглавную. [Подробнее этот момент разбирается в четвертой главе книги]

SVG-иконка размещается на текстовом поле с помощью CSS. В Internet Explorer SVG-элемент по умолчанию доступны для фокусировки с клавиатуры, поэтому устанавливаем атрибут focusable="false".

Атрибуты меню

  • Атрибут role="list" определяет меню как список опцией, каждая опция имеет атрибут role="option".
  • Атрибут aria-selected="true" сообщает пользователю, какая опция в списке выделена в данный момент. Значение может переключаться между true и false.
  • Атрибут tabindex="-1" означает, что фокус на опциях может быть установлен программно, при нажатии пользователем определенных клавиш. Этим мы займемся чуть позже.
  • Атрибут data-option-value содержит значение конкретной опции. Когда пользователь нажимает на нее, значение элемента select будет обновлено на значение опции. Таким образом мы синхронизируем видимый интерфейс и скрытый контрол, который будет отправлен на сервер.

Используем live region, чтобы скринридеры знали, когда отображается список опций

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

Чтобы предоставить всем пользователям одинаковый опыт (первый принцип инклюзивного дизайна), мы будем использовать live region [описано в главе “Форма оформление заказа”].

После создания меню в live region (элемент с атрибутом aria-live) будет указано количество доступных результатов (“13 результатов доступно”). Имея эту информацию, пользователь может самостоятельно принять решение: продолжить печатать, чтобы конкретизировать свой выбор, или посмотреть список и выбрать предложение из него.

Так как эта информация нужна только для пользователей скринридеров, мы скрываем ее от других пользователей с помощью класса visually-hidden.

Обработка пользовательского ввода

Когда пользователь вводит текст в поле ввода, мы должны отслеживать каждое нажатие на клавиши.

        Autocomplete.prototype.createTextBox = function() {
  this.textBox.on('keyup', $.proxy(this, 'onTextBoxKeyUp'));
};

Autocomplete.prototype.onTextBoxKeyUp = function(e) {
  switch (e.keyCode) {
    case this.keys.esc:
    case this.keys.up:
    case this.keys.left:
    case this.keys.right:
    case this.keys.space:
    case this.keys.enter:
    case this.keys.tab:
    case this.keys.shift:
      // игнорировать, иначе появится меню
      break;
    case this.keys.down:
      this.onTextBoxDownPressed(e);
      break;
    default:
      this.onTextBoxType(e);
  }
};
    

Объект this.keys – это коллекция числовых кодов, соответствующих конкретным клавишам. Мы специально используем именованные поля объекта, чтобы избежать магических чисел и сделать код понятнее.

С помощью конструкции switch отфильтровываем нажатия на клавиши Escape, Enter, Tab, Shift, Пробел и стрелки Вверх, Влево и Вправо. Если этого не сделать, то запустится код из секции default, и откроется меню с предложениями.

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

В основном нас интересуют две последних секции case: нажатие на клавишу Down (стрелка вниз) [будет разобран чуть позже] и дефолтный кейс – нажатие на обычные клавиши (буквы, цифры, знаки препинания и все такое). В последнем случае мы вызываем метод onTextBoxType():

        Autocomplete.prototype.onTextBoxType = function(e) {
  // опции отображаются только если в поле что-то введено
  if(this.textBox.val().trim().length > 0) {
    // получаем список подходящих опций
    var options = this.getOptions(this.textBox.val().trim().toLowerCase());

    // рендерим список
    this.buildMenu(options);

    // показываем меню
    this.showMenu();

    // обновляем live region
    this.updateStatus(options.length);
  }

  // обновляем значение элемента select
  this.updateSelectBox();
};

    

Метод getOptions() [описан чуть дальше в тексте] отфильтровывает только те опции, которые совпадают с пользовательским вводом.

Единый tab-стоп для составных контролов

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

У таких сложных контролов должен быть только один tab-стоп, как говорит спецификация WAI-ARIA Authoring Practices 1.1:

Основное соглашение о навигации с клавиатуры, общее для всех платформ, заключается в том, что клавиши tab и shift+tab перемещают фокус с одного компонента пользовательского интерфейса на другой, в то время как другие клавиши, в первую очередь клавиши стрелок, перемещают фокус внутри компонентов, если они включают несколько интерактивных элементов. Путь, по которому перемещается фокус при нажатии на tab, – это последовательность табов (tab sequence), или кольцо табов (tab ring).

Группа радио-кнопок – это тоже составной контрол.

Как только происходит фокусировка на первой радио-кнопке, пользователь может перемещаться между ними с помощью кнопок-стрелок. Нажатие на Tab должно переводить фокус на следующий элемент в последовательности табов [а не на следующую радио-кнопку в группе].

Вернемся к автокомплиту.

Фокус на поле ввода устанавливается естественным образом при нажатии на Tab. Далее пользователь может использовать стрелки, чтобы перемещаться по меню опций. Нажатие на Tab должно приводить к закрытию меню (если оно открыто), чтобы оно не закрывало контент, находящийся под ним.

ARIA activedescendant не работает

Многие компоненты используют атрибут aria-activedescendant как альтернативный способ убедиться, что они имеют только один tab-стоп. Этот атрибут сохраняет фокус на контейнере компонента и ссылается на текущий активный элемент.

Для нашего компонента это не подходит, так как поле ввода это соседний элемент для меню – а не его родитель.

Скрытие меню по событию onblur не работает

Событие onblur возникает, когда элемент теряет фокус. В нашем случае мы можем прослушивать это событие на текстовом поле – оно происходит, если пользователь покидает поле, нажав Tab, или кликнув за его пределами.

        this.textBox.on('blur', function(e) { // спрятать меню });

    

К сожалению, программное перемещение фокуса с поля на меню тоже вызывает событие blur, что приводит к скрытию меню и делает его недоступным для пользователей клавиатуры.

Решением может стать использование setTimeout(). Мы устанавливаем задержку, и если за это время пользователь переместит фокус на список, то мы сбросим таймер с помощью clearTimeout и не будем закрывать меню.

        this.textBox.on('blur', $.proxy(function(e) {
  // задержка до закрытия меню
  this.timeout = window.setTimeout(function() {
    // закрыть меню
  }, 100);
}, this));

this.menu.on('focus', $.proxy(function(e) {
  // отмена закрытия меню
  window.clearTimeout(this.timeout);
}, this));
    

Но это не работает в iOS 10 из-за проблем с событием blur. Оно некорректно вызывается, если пользователь закрывает экранную клавиатуру, так что в меню предложений все равно нельзя попасть.

Есть другое решение.

Скрытие меню по нажатию на Tab

Вместо того, чтобы прятать меню при потере фокуса полем ввода, мы можем использовать событие keydown и отслеживать нажатия на клавишу Tab.

        this.textBox.on('keydown', $.proxy(function(e) {
  switch (e.keyCode) {
    case this.keys.tab:
      // спрятать меню
      break;
  }
}, this));
    

Но в отличие от события blur, это решение не учитывает случаи, когда пользователь кликает где-то вне поля ввода, из-за чего оно теряет фокус.

Поэтому мы должны добавить еще обработчик кликов для всего документа и проверять, где именно сделан клик:

        $(document).on('click', $.proxy(function(e) {
  if(!this.container[0].contains(e.target)) {
    // спрятать меню
  }
}, this));
    

Нажатие на стрелку Вниз для перемещения в меню

Когда поле ввода находится в фокусе, нажатие на стрелку Вниз вызывает метод onTextBoxDownPressed(), который перемещает пользователя в меню.

        Autocomplete.prototype.onTextBoxDownPressed = function(e) {
  var option;
  var options;
  var value = this.textBox.val().trim();

  /*
    Если значение пустое или точно совпадает с опцией,
    показываем целое меню    
  */

  if(value.length === 0 || this.isExactMatch(value)) {

    // получаем список опций
    options = this.getAllOptions();

    // рендерим меню
    this.buildMenu(options);

    // показываем меню
    this.showMenu();

    // берем первую опцию
    option = this.getFirstOption();

    // подсвечиваем первую опцию
    this.highlightOption(option);

  /*
    Если значение есть и оно не совпадает с опцией,
    показываем только подходящие опции
  */

  } else {

    // получаем список опций
    options = this.getOptions(value);

    // если есть подходящие опции
    if(options.length > 0) {

      // рендерим меню
      this.buildMenu(options);

      // показываем меню
      this.showMenu();

      // получаем первую опцию
      option = this.getFirstOption();

      // подсвечиваем первую опцию
      this.highlightOption(option);
    }
  }
};

    

Если пользователь нажимает клавишу Вниз, ничего не напечатав, открывается полное меню со всеми опциями. Первая опция в списке получает фокус [метод highlightOption будет рассмотрен далее].

То же самое происходит, если введенное значение точно совпадает с какой-либо опцией, хотя это бывает довольно редко. Большинство пользователей предпочитают выбирать значение из списка, так как это быстрее.

В остальных случаях мы показываем только подходящие опции (если они есть) и также фокусируемся на первой опции в списке.

Прокрутка меню

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

        .autocomplete [role=listbox] {
  max-height: 12em;
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

    

Свойство max-height ограничивает максимальную высоту меню. Если контент превышает эти размеры, то появляется вертикальный скролл (overflow-y: scroll).

Последнее нестандартное свойство разрешает импульсную прокрутку (momentum scrolling) в iOS. Таким образом, список с предложениями будет прокручиваться так же, как и все остальные элементы.

Выбор опции

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

        Autocomplete.prototype.createMenu = function() {
  this.menu.on('click', '[role=option]', $.proxy(this, 'onOptionClick'));
};

Autocomplete.prototype.onOptionClick = function(e) {
  var option = $(e.currentTarget);
  this.selectOption(option);
};

    

Обработчик события извлекает опцию, по которой кликнули (e.currentTarget) и передает ее методу selectOption.

        Autocomplete.prototype.selectOption = function(option) {
  var value = option.attr('data-option-value');
  this.setValue(value);
  this.hideMenu();
  this.focusTextBox();
};

    

Метод selectOption извлекает значение опции из атрибута data-option-value, передает его методу setValue, который устанавливает его в поле ввода и в скрытый select. Меню закрывается, фокус перемещается на поле ввода.

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

Взаимодействие с меню с клавиатуры

Если фокус находится внутри меню, пользователь может перемещаться по нему с помощью клавиатуры. Для этого мы должны прослушивать событие keydown.

        Autocomplete.prototype.createMenu = function() {
  this.menu.on('keydown', $.proxy(this, 'onMenuKeyDown'));
};

Autocomplete.prototype.onMenuKeyDown = function(e) {
  switch (e.keyCode) {
    case this.keys.up:
      // ...
      break;
    case this.keys.down:
      // ...
      break;
    case this.keys.enter:
      // ...
      break;
    case this.keys.space:
      // ...
      break;
    case this.keys.esc:
      // ...
      break;
    case this.keys.tab:
      // ...
      break;
    default:
      this.textBox.focus();
  }
};

    
Клавиша Действие
Up Если первая опция находится в фокусе, то установить фокус на текстовое поле. Иначе установить фокус на предыдущую опцию в списке.
Down Установить фокус на следующую опцию. Если активная опция последняя в списке, то ничего не делать.
Tab Спрятать меню.
Enter or Space Выбрать опцию, которая сейчас активна, и установить фокус на поле ввода.
Escape Спрятать меню и установить фокус на поле ввода.
Everything else Установить фокус на поле ввода, чтобы пользователь мог продолжить печатать.

Выделение активной опции

Когда пользователь фокусируется на опции, нажимая клавиши Вверх и Вниз, вызывается метод highlightOption().

        Autocomplete.prototype.highlightOption = function(option) {
  // если активная опция уже есть
  if(this.activeOptionId) {

    // получить активную опцию
    var activeOption = this.getOptionById(this.activeOptionId);

    // убрать с нее выделение
    activeOption.attr('aria-selected', 'false');
  }

  // установить выделение для новой активной опции
  option.attr('aria-selected', 'true');

  // Если опция не видна в меню
  if(!this.isElementVisible(option.parent(), option)) {

    // прокрутить меню, чтобы опция была видна
    option.parent().scrollTop(option.parent().scrollTop() + option.position().top);
  }

  // сохранить идентификатор текущей активной опции
  this.activeOptionId = option[0].id;

  // переместить фокус на нее
  option.focus();
};
    

Этот метод выполняет сразу несколько задач.

Для начала он проверяет, выделена ли уже какая-то опция. Если да, то значение ее атрибута aria-selected изменяется на false. Это гарантирует, что скринридеры узнают об изменениях.

Затем для выбранной опции aria-selected изменяется на true.

Так как меню имеет фиксированную высоту, новая активная опция может находиться за пределами видимой зоны. Мы проверяем это с помощью метода isElementVisible(). Если опция не видна, регулируем прокрутку с помощью scrollTop.

Далее сохраняем новую активную опцию, чтобы сослаться на нее в следующий раз при вызове метода. И наконец, устанавливаем фокус на нее, чтобы убедиться, что скринридеры оповещены о новом значении.

Чтобы сообщить об изменениях пользователям, использующим монитор, устанавливаем отдельные стили для выделенной опции:

        .autocomplete [role=option][aria-selected="true"] {
  background-color: #005EA5;
  border-color: #005EA5;
  color: #ffffff;
}

    

Связывание состояния и стиля – хороший прием, который прямо синхронизирует функциональность и ее представление.

Фильтрация опций

Хорошая фильтрация должна прощать пользователю мелкие опечатки и перепутанные буквы.

Как вы помните, данные, из которых составляется список предложений находятся в элементах option внутри скрытого селекта.

        <select>
  <option value="">Select</option>
  <option value="1">France</option>
  <option value="2">Germany</option>
</select>

    

Когда нужно отобрать опции, соответствующие пользовательскому вводу, мы вызываем метод getOptions().

        Autocomplete.prototype.getOptions = function(value) {
  var matches = [];

  // Цикл по всем элементам option
  this.select.find('option').each(function(i, el) {
    el = $(el);

    // если у опции есть значение
    // и текст опции совпадает с пользовательским текстом 
    if(el.val().trim().length > 0 && el.text().toLowerCase().indexOf(value.toLowerCase()) > -1) {

      // добавляем ее в массив совпадений
      matches.push({ text: el.text(), value: el.val() });
    }
  });

  return matches;
};
    

Метод принимает в качестве параметра текст, введенный пользователем. Затем он перебирает все элементы option и сравнивает их текст с пользовательским.

Для проверки мы используем метод indexOf(), который ищет подстроку в строке. То есть пользователь может ввести только часть названия страны и все равно получит подходящие предложения.

Перед сравнением все значения приводятся к нижнему регистру, а начальные и конечные пробелы обрезаются (метод trim). Таким образом, пользователь может использовать и строчные, и прописные символы – например, у него может быть включен режим Caps Lock.

Все подходящие опции добавляются в массив matches, который будет использован для рендера меню.

Поддержка эндонимов и опечаток

Эндонимы – это “местные” названия географических объектов. Например, в английском языке Германия – это Germany, а в немецком Deutschland.

Пятый принцип инклюзивного дизайна гласит – “Предоставь выбор”. Так что мы можем позволить пользователям использовать эндонимы.

Прежде всего, их нужно как-то обозначить. Например, в data-атрибуте элемента option.

        <select>
  <!-- другие опции -->
  <option value="2" data-alt="Deutschland">Germany</option>
  <!-- другие опции -->
</select>

    

Теперь изменим немного функцию фильтрации и добавим в нее проверку альтернативных значений:

        Autocomplete.prototype.getOptions = function(value) {
  var matches = [];

  // Цикл по элементам option
  this.select.find('option').each(function(i, el) {
    el = $(el);

    // если у опции есть значение
    // и текст опции совпадает с пользовательским текстом 
    // или значение атрибута data-alt совпадает с пользовательским текстом
    if( el.val().trim().length > 0
  	&& el.text().toLowerCase().indexOf(value.toLowerCase()) > -1
  	|| el.attr('data-alt')
  	&& el.attr('data-alt').toLowerCase().indexOf(value.toLowerCase()) > -1 ) {

      // добавляем ее в массив совпадений
      matches.push({ text: el.text(), value: el.val() });
    }
  });

  return matches;
};

    

Вы можете сделать то же самое для распространенных опечаток в названиях стран, если хотите.

***

Демо-версию созданного контрола автодополнения можно найти здесь.

02
Сен
2021

Как лучше сделать задержку v-if на vue?

Всем привет
Vue проект. Задача стоит – скрывать сайдбар по клику. Я сделал по типу hideSidebar = !hideSidebar. И при клике, часть элементов должны скрываться, а сам сайдбар становиться уже. Все работает хорошо, но вот когда я тыкаю кнопку …

01
Сен
2021

Конференция «Суровый код из Омска»

Конференция для разработчиков, DevOps-специалистов, системных аналитиков, тестировщиков, техлидов и инженеров. В программе доклады от спикеров из Huawei, Райффайзенбанка, Microsoft, ЦИАН и Luxoft, программирование в прямом эфире и CTF-соревнование.
— Ч…

30
Авг
2021

Не работает classlist.add

Что делать при нажатие на кнопку текст должен двигаться в право а потом обратно вернуться но он двигается когда загружен сайт помогите пожалуйста кто поможет большое спасибо
Html:
<!DOCTYPE html>
<html lang="en">
<…

30
Авг
2021

React Native expo проверка наличия записи в базе данных

я работаю над React Native приложением для считывания QR кодов. У меня подключена база SQLite, в которую записываются все сканы. Мне необходимо проверить QR на повторное считывание.
if (checkDouble(scan)) { // если позиция еще не сканирова…

30
Авг
2021

Передача параметров в функцию JavaScript

Друзья, всем привет!
Изучаю JS, возник следующий вопрос:
почему в результате выполнения следующего кода, массив arr не заквадратится?
вроде ж по ссылке передаются аргументы…
Всем спасибо за помощь)
var arr = [1, 2, 3, 4, 5];

function te…

30
Авг
2021

Как заполнить pdf документ из данных формы?

Есть форма на сайте. Пользователь заполняет её. Эти данные, из формы, вносятся в pdf файл в определенные места. Например, есть сертификат, готовый, в котором поле с ФИО пустое, пользователь вводит свое ФИО в input на сайте, и это ФИО попад…