Мультиселект и автокомплит на AngularJS
Со списками множественного выбора на Ангуляре всегда было неважно. Существующие решения либо обертки над jQuery-плагином, либо выглядят как не пойми что, либо просто корявы. И у всех естественно особое уникальное АПИ, как будто пользователям делать больше нечего как вникать в ход мыслей разработчиков каждого плагина. Меня такое положение дел не устроило, поэтому написал свой велосипед. Спустя год он дозрел до публикации.
Та-дам! (и забавная история вконце)
tamtakoe.github.io/oi.select
Прежде всего решил: никакого своего АПИ, никаких сторонних библиотек и никакого своего дизайна. Селект должен быть максимально приближен к стандартному только с возможностью автодополнения и создания в нем новых опций. АПИ так же должен быть совместим с Angular select, который в свою очередь совместим с HTML select. Стандартный внешний вид использовать не получилось, т. к. он определяется браузером, поэтому взял за основу наиболее распространенный Bootstrap. По-сути, получился не новый компонент, а расширение существующего.
Необходимо ценить время своих коллег, не вынуждать их изучать новый формат параметров, чтобы сделать то, что они давно умеют делать; считаться с навыками пользователей, которые ожидают от компонента привычного вида и поведения. Первое, что должен сделать разработчик при проектировании элементов интерфейса — убить в себе дизайнера. Не в том плане, чтобы сделать дизайн кое-как, а чтобы максимально использовать существующее поведение, добавлять от себя в крайнем случае, а менять в самом крайнем.
АПИ получилось сделать полностью совместимым, за исключением задания опций через . В HTML такой способ используется из-за того, что другого нет. В Ангуляре же есть контроллеры и модели, там этот способ был оставлен для совместимости. Мне было лень делать такую совместимость. Может быть когда нибудь…
В самом начале возникло три пути создания подобного компонента: реализация на чистом JS с манипуляциями DOM и проч., расширение ангуляровской директивы select, реализация преимущественно на Ангуляре. Т.к. я не настолько умен, чтобы принимать такие решения в голове, то просто взял и реализовал все три способа:
- Самое заманчивое — написать свой select по аналогии с ангуляровским. Чистый JS, максимальная производительность, всё под контролем. Но сделать такое оказалось не просто: слишком много нюансов, требуется знание Ангуляра на самом низком уровне, много копипаста функций, которые Ангуляр реализует внутри. В итоге все это вываливается в многие тысячи строчек кода со всеми вытекающими. Отказался от этой затеи, хотя для простых компонентов она бы подошла.
- Можно использовать в своей директиве Directive Definition Object ангуляровского select. Расширить его, переопределить методы и т. п. Звучит хорошо, но на деле получается слишком костыльно. Все-таки Ангуляр пока не дает возможность для расширения своих компонентов, особенно директив (к сожалению), поэтому расширяя средствами JS вы завязываетесь на внутреннюю реализацию и рискуете потерей обратной совместимости. Такой способ допустим, в директивах, где расширение предусмотрено разработчиками, например Angular-bootstrap popover.
- Проще и нагляднее оказалась реализация средствами Ангуляра. Из копипаста только Regexp для парсинга параметров из
ng-options
. Код проще чем на чистом JS и не требует знания внутреннего устройства ангуляровского селекта. Производительность хорошая (за что я больше всего боялся). Думаю, этот способ подойдет для реализации большинства компонентов.
Не смотря на совместимость по АПИ, уникальных параметров хватает. Полагаю, там еще не всё гладко и очевидно, так что буду рад замечаниям в комментариях. Много чего относится к созданию новых элементов из строки ввода. После некоторого анализа пришел к выводу, что существует два случая использования поля ввода с подсказками:
prompt
— поле работает как обычный инпут, а в списке просто выводятся подсказки. По нажатию Enter в модель попадает значение из поля. Поле можно очистить и тогда в модели будет пустая строка. Такое поведение характерно для поисковой формы.autocomplete
— поле работает как список с вариантами. По нажатию Enter в модель попадает первый вариант из списка и только если там ничего не было — содержимое поле ввода. Записать в модель пустую строку нельзя. Такое поведение характерно для формы ввода тегов.
Очередной спорный пункт: кастомное оформление опций. В стандартном компоненте (даже ангуляровском, даже бутстраповском) ничего подобного нет. Но всем хочется. Пришел к компромиссному решению — сделать поддержку фильтров для всего и вся. Конечно, придется писать HTML в JS, зато и быстродействие выше и значительно проще чем заморачиваться с поддержкой шаблонов (да и какие могут быть шаблоны для элемента списка?). Впрочем, когда-нибудь…
А теперь обещанная забавная история для осиливших страницу текста без картинок.
В одной из версий была бага, которую никто из разработчиков веб-отдела не мог воспроизвести. Зато остальные сотрудники: менеджеры, аналитики, тестировщики, 1С-программисты воспроизводили в любом случае. Доходило до слез — за одним компьютером одни люди всегда выбирают элемент из списка, у других это ни получается как бы они ни старались. Думаю, это свойство можно использовать при приеме людей на работу. Плохие программисты тест не пройдут. Проверь себя и ты, читатель.