Со списками множественного выбора на Ангуляре всегда было неважно. Существующие решения либо обертки над jQuery-плагином, либо выглядят как не пойми что, либо просто корявы. И у всех естественно особое уникальное АПИ, как будто пользователям делать больше нечего как вникать в ход мыслей разработчиков каждого плагина. Меня такое положение дел не устроило, поэтому написал свой велосипед. Спустя год он дозрел до публикации.
Та-дам! (и забавная история вконце)
tamtakoe.github.io/oi.select
Прежде всего решил: никакого своего АПИ, никаких сторонних библиотек и никакого своего дизайна. Селект должен быть максимально приближен к стандартному только с возможностью автодополнения и создания в нем новых опций. АПИ так же должен быть совместим с Angular select, который в свою очередь совместим с HTML select. Стандартный внешний вид использовать не получилось, т. к. он определяется браузером, поэтому взял за основу наиболее распространенный Bootstrap. По-сути, получился не новый компонент, а расширение существующего.
АПИ получилось сделать полностью совместимым, за исключением задания опций через
В самом начале возникло три пути создания подобного компонента: реализация на чистом JS с манипуляциями DOM и проч., расширение ангуляровской директивы select, реализация преимущественно на Ангуляре. Т.к. я не настолько умен, чтобы принимать такие решения в голове, то просто взял и реализовал все три способа:
Не смотря на совместимость по АПИ, уникальных параметров хватает. Полагаю, там еще не всё гладко и очевидно, так что буду рад замечаниям в комментариях. Много чего относится к созданию новых элементов из строки ввода. После некоторого анализа пришел к выводу, что существует два случая использования поля ввода с подсказками:
Очередной спорный пункт: кастомное оформление опций. В стандартном компоненте (даже ангуляровском, даже бутстраповском) ничего подобного нет. Но всем хочется. Пришел к компромиссному решению — сделать поддержку фильтров для всего и вся. Конечно, придется писать HTML в JS, зато и быстродействие выше и значительно проще чем заморачиваться с поддержкой шаблонов (да и какие могут быть шаблоны для элемента списка?). Впрочем, когда-нибудь…
А теперь обещанная забавная история для осиливших страницу текста без картинок.
В одной из версий была бага, которую никто из разработчиков веб-отдела не мог воспроизвести. Зато остальные сотрудники: менеджеры, аналитики, тестировщики, 1С-программисты воспроизводили в любом случае. Доходило до слез — за одним компьютером одни люди всегда выбирают элемент из списка, у других это ни получается как бы они ни старались. Думаю, это свойство можно использовать при приеме людей на работу. Плохие программисты тест не пройдут. Проверь себя и ты, читатель.
Та-дам! (и забавная история вконце)
tamtakoe.github.io/oi.select
Прежде всего решил: никакого своего АПИ, никаких сторонних библиотек и никакого своего дизайна. Селект должен быть максимально приближен к стандартному только с возможностью автодополнения и создания в нем новых опций. АПИ так же должен быть совместим с Angular select, который в свою очередь совместим с HTML select. Стандартный внешний вид использовать не получилось, т. к. он определяется браузером, поэтому взял за основу наиболее распространенный Bootstrap. По-сути, получился не новый компонент, а расширение существующего.
Необходимо ценить время своих коллег, не вынуждать их изучать новый формат параметров, чтобы сделать то, что они давно умеют делать; считаться с навыками пользователей, которые ожидают от компонента привычного вида и поведения. Первое, что должен сделать разработчик при проектировании элементов интерфейса — убить в себе дизайнера. Не в том плане, чтобы сделать дизайн кое-как, а чтобы максимально использовать существующее поведение, добавлять от себя в крайнем случае, а менять в самом крайнем.
АПИ получилось сделать полностью совместимым, за исключением задания опций через
<option>
. В 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С-программисты воспроизводили в любом случае. Доходило до слез — за одним компьютером одни люди всегда выбирают элемент из списка, у других это ни получается как бы они ни старались. Думаю, это свойство можно использовать при приеме людей на работу. Плохие программисты тест не пройдут. Проверь себя и ты, читатель.
Zdomb
Сделано аккуратненько, молодцы!
tamtakoe
Дело там не в дабл клике, хотя он, наверное, может помочь. Нужно выбирать обычным кликом)
a553
Смог воспроизвести баг. Но как он к программистам/не-программистам относится я не понял.
tamtakoe
owniumo
Потом я нашёл надёжный способ выбрать любой пункт —
1.открыть дропдаун
2. не торопясь посмотреть на варианты
3. тут надо успеть за 100мс: нажать/зажать ctrl или shift или alt и сразу кликнуть мышкой
также работает если зажать кнопку заранее, потом отпустить и быстро кликнуть, но намного менее надёжно
tamtakoe
У меня и моих коллег получалось в 100% случаев обычным щелчком. Специально хотели воспроизвести баг, но не могли. Только потом, когда понял в чем суть, удалось воспроизвести)
makcums
Долго не мог понять в чём дело, но всё же баг воспроизвёл. Посему вопрос: как можно было так криво сделать?
tamtakoe
Это появилось из-за хака для всех браузеров кроме Хрома. В этих браузерах нельзя понять чем было вызвано событие blur, переводом фокуса или программно, поэтому пришлось проверять через некоторый промежуток времени что инпут действительно потерял фокус и триггерить blur на элементе селекта. Причем из-за особенностей браузерной работы с событиями этот промежуток времени не маленький. С 10 мс не работало, с 50 мс через раз. github.com/tamtakoe/oi.select/blob/0.2.8/src/services.js#L99
youlose
А какая разница как вызывается blur? Зачем этот хак?
tamtakoe
Без него в FF, IE и проч. не будет работать переход по табу. Т.е. фокус сместится с одного инпута на другой, но компонент об этом никак не узнает, т. к. нет элемента в event.relativeTarget и он подумает, что blur был вызван программно и не станет вызывать его второй раз.
Magister7
А я, кажется, второй баг нашел — на странице «Funny» выбор с клавиатуры (нажатием Enter) не работает вообще никак.
Решил что я, наверное, совсем плохой программист )))
Magister7
вот блин, внизу уже ответили. извиняюсь, недочитал комментарии…
a553
На странице Customization не скроллится список тачем, если при открытом списке тапнуть на строку. Win 10 + Firefox 41
tamtakoe
Это на телефоне?
a553
На десктопе.
tamtakoe
Странно. В последней версии тоже?
artemmalko
Пара багов:
1) 938 пикселей по ширине экран www.dropbox.com/s/qupszykt884axpb/Screenshot%202015-08-30%2013.24.55.png?dl=0
2) Не открывается вверх, если места снизу нет www.dropbox.com/s/k8vbs275eirphyt/Screenshot%202015-08-30%2013.25.31.png?dl=0
3) Если открыть выпадашку стрелками, то автокамплит не работает, нельзя ввести значение, можно только выбрать. Хотя, это может быть и фича такая.
Пара замечаний:
1) На мобильных устройствах желательно оставлять оригинальный селект.
2) Если у контейнера, в котором лежит селект будет overflow:hidden, то могут быть неприятные баги. Например, обрезанная выпадашка.
3) Еще не нашел оригинального селекта на странице. Он удаляется?
tamtakoe
Баги:
1) поправлю
2) Стандартный селект не поднимается наверх
Но идея хорошая, нужно будет сделать с помощью CSS.
3) не удалось повторить. Это в каком примере наблюдается?
Замечания:
1) В мобильных устройствах так же нет автокомплита, поэтому стандартный селект не прокатит.
2) Не знаю как это побороть и надо ли. Тут, наверное, будет лучше сделать вариант с выпадашкой наверх.
3) Стандартного селекта там нет. С ним получалось слишком костыльно.
artemmalko
2) Открывается наверх, попробуйте сделайте селект в самом низу страницы, без открытых дев-тулзов. Он откроется вверх.
3) В любом. Просто делаете фокус на селект и нажимаете стрелку вниз. Селект откроется, а написать ничего нельзя.
Про замечания:
1) Видимо стоит только для этого случая оставить кастомный. В остальных случаях 100% удобнее пользоваться нативным.
2) Стоит, неизвестно, в каком месте селект будет вызываться. Побороть легко, просто держите выпадашку в самом низу body и подцепляйте ее при открытии к селекту.
3) А в чем были костыли?
tamtakoe
3) Странно. У меня можно писать и в Хроме и в ФФ. Что за система/браузер?
Подумаю, чтобы аппендить выпадашку к body или сделать такую опцию как в angular-bootstap для тултипов
Пробовал способ, когда в основе лежит стандартный селект, скрытый из виду, и в него копируются опции из oi-select. Нужно было перекомпилировать элемент, следить чтобы связь между областями видимости не порвалась при использовании, например, ng-if на директиве. Код был сложнее для восприятия, да и смысла не было, т. к. всё равно почти вся функциональность не была привязана к стандартному селекту.
artemmalko
tamtakoe.github.io/oi.select/#/select/#grouping клик на стрелку или открытие клавишами не дает вводить ничего. Mac Ось, любой браузер.
tamtakoe
А, на эту стрелку. Поправлю в следующей версии
lega
Почему не взяли select2?
tamtakoe
Во-первых, он на jQuery, который уже как пол года назад удалось выпилить из проекта. Из jQuery нужна была только функция определения высоты элемента (меньше 100 строчек). Тащить ради нее всю библиотеку как-то странно.
Во-вторых, никакой совместимости по АПИ там и близко нет. Пришлось бы писать огромный адаптер или мучиться.
В-третьих, слишком много отсебятины. Для множественного выбора поле ввода находится в строке с выбранными элементами, для одиночного — в списке, чтобы удалить тег нужно попасть по маленькому крестику, какие-то крестики для одиночного ввода… Не продуманный дизайн. Такое ощущение, что его писали разные люди, которые не смогли договориться.
ZOXEXIVO
А как насчет angular-ui-select?
tamtakoe
Практически то же самое. Год назад он был самым вменяемым, но, по сути, представлял обертку над select2, тащил jQuery и был глючным. Сейчас плагины подросли, но до уровня oi.select все еще не дотягивают. Пока ни один плагин не учитывает, что у Ангуляра есть готовое АПИ для селекта и предлагает свое, а это показатель уровня разработчиков.
P. S. Понравился в свое время brianreavis.github.io/selectize.js, с него скопировал лучшие наработки в дизайне.
Methos
Кстати, на jquery мне нравится chosen
tamtakoe
По-моему, слишком много отсебятины. Какие-то крестики для очистки модели в одиночном инпуте. Для одиночного и множественного селекта поля поиска выглядят по-разному, список не закрывается по щелчку на пустом поле ввода (родной селект так себя не ведет). И как-то всё неаккуратно.
Methos
очистка в одиночном удобно, если есть несколько зависимых списков.
например,
страна — город — улица
тогда при очистке страны очищается и страна, и все зависимые.
одним кликом
tamtakoe
Показываю крестик в варианте
cleanModel: true
(http://tamtakoe.github.io/oi.select/#/select/#cleanmodel)Там модель очищается при клике в любом месте. Не сторонник маленьких кнопочек и крестиков, по которым фиг попадешь, особенно на мобильном устройстве)
Methos
А есть так, чтобы было как это tamtakoe.github.io/oi.select/#/select/#editableoptions, но с возможностью добавлять свои таги, а не только те, которые есть в списке?
tamtakoe
См. prompt, autocomplete. Об особенностях описано в статье
Methos
у вас здесь отсутствует запятая
tamtakoe
Поправил
Methos
вероятно, здесь в конце нужно писать newItemFn?
tamtakoe
Поправил
Methos
какой-то странный баг — добавляет только ОДНО, первое значение. а дальше — ничего…
и только если редактируешь и опять добавляешь, тогда начинает добавлять и дальше.
Methos
Это решено, у меня были дубли в id.
Methos
Как customize delete button?
tamtakoe
delete button на самом деле нет. Ее добавляет фильтр по-умолчанию
В
selectFilter
можно переопределить на свой фильтр и делать там что угодноMethos
Почему, когда фокус убирается с поля ввода, элемент добавляется? Возможно ли сделать так, чтобы добавлялся только по enter?
tamtakoe
Вообще, не должен. Разве что в случае с одиночным селектом. Он показывает выбранное ранее значение. Это сделано специально, т. к. так ведет себя обычный селект
Methos
Баг — если редактируешь значение, то если в это время удалить другой элемент, то пропадают оба элемента.
tamtakoe
Сейчас если стоит режим редактирования элементов, то удалить элемент как-бы нельзя. Т. е. из модели он удаляется, но на его месте остается его текст. Если вводить какой либо текст и удалить при этом любой элемент, то вводимый ранее текст заменится на текст удаленного элемента. Должно так работать
Methos
how to customized full own template.html?
tamtakoe
template.html очень не рекомендуется заменять на свой, т. к. это может нарушить работу элемента. Постарался сделать так, чтобы внешний вид можно было полностью настроить фильтрами и стилями.
Methos
спасибо, буду пытать
Methos
и ещё
newItemModel: {id: null, name: $query},
как-бы id автоинкремент сделать?
update: понял, использовать newItemFn
sajgak
по поводу бага — выбор по enter тоже не работает))
tamtakoe
Я его специально запретил в том примере, чтобы не хитрили)
serf
Вот такое еще есть mbenford.github.io/ngTagsInput/demos
tamtakoe
Неплохой мультиселект. Некоторые примеры ведут себя не очевидным образом. У меня на первых порах тоже такое было. А вот валидацию текстового ввода возьму на заметку.
Dzorogh
Спасибо за классную реализацию сохранения новой модели (http://tamtakoe.github.io/oi.select/#/select/#prompt).
Давно пытался найти такой плагин.
Сейчас очень серьезно размышляю о переходе с angular-ui/ui-select на ваше решение в своем большом проекте.
Подскажите, можно ли сделать вывод списка вот в таком формате:
Скриншот из демки ui-select
Нужно, чтобы в выпадающем меню выводилось несколько разных параметров, согласно моему шаблону, и чтобы работал фильтр (поиск) по всем параметрам (или, как это можно в ui-select, по тем, которые нужны).
Я нашел только такой пример — http://tamtakoe.github.io/oi.select/#/select/#customization, но там фильтр не работает совсем.
tamtakoe
Ответил случайно в корень, а не в эту тему. Запилил простенькую реализацию поиска по другим полям для плоских объектов tamtakoe.github.io/oi.select/#/select/#filtered
Dzorogh
Спасибо за ответ и пример.
tamtakoe
Для форматирования выбранных тегов нужно использовать
searchFilter
, для вариантов в списке —dropdownFilter
. Поиск осуществляется только по одному полю, указанному вoi-options
(что полностью соответствуютng-options
), но можно самому формировать поисковую выдачу:— задать в качестве списка функцию, принимающую поисковую строку и возвращающую список tamtakoe.github.io/oi.select/#/select/#lazyloading
— переопределить
listFilter
в который передается поисковая строка и список в которой так же можно как угодно этот список фильтровать tamtakoe.github.io/oi.select/#/select/#customizationВообще, подумаю, чтобы расширить встроенный
listFilter
, чтобы туда передавать параметры для поиска по другим полям.P. S. Как минимум в одном большом проекте это решение уже используется — в моём, так что если какие-то серьезные баги всплывают, узнаю об этом очень быстро)
Methos
Можно ли сделать autocomplete по ajax?
tamtakoe
Запросто. Возвращай промис в функции, формирующей список и ищи в базе по строке tamtakoe.github.io/oi.select/#/select/#lazyloading
Methos
ok, thanks
Methos
А как сделать так, чтобы oi-options первоначально принял массив, а уже потооом был бы autocomplete?
tamtakoe
функция, возвращающая список вариантов по строке должна возвращать все варианты, если строка пустая… Если правильно понял что было нужно
Methos
как повесить на Enter обработчик события?
tamtakoe
Никак нельзя (конечно, можно обернуть все в свою директиву или найти элемент селекта в контроллере, а там найти инпут и повеситься на Enter, но это нехорошо).
Нужно следить за изменением модели с помощью
watch
и выполнять нужные действия. Так код будет максимально независим от элемента селекта.Methos
Вы могли бы добавить в код возможность подписки на событие keyDown в поле ввода?
Methos
И ещё есть баг — если в пустое поле ввода сделать фокус, то вызывается функция autocomplete с пустым запросом. Думаю, нужно сделать так, чтобы не вызывалась.
Methos
К сожалению, пришлось отказаться от вашего плагина по причине отсутствия API событий и возможности расширения.
Сделал на коленке за часик свой редактор тагов.