После автокомплитера конечно же захотелось сделать такой же простой и удобный контрол для ввода тэгов.
Причем сделать еще одну фичу в нем, которой мне очень не хватало в старом добром select2 - это возможность проверять тэги на ходу. Т.е. например человек не выбрал из списка, а просто написал тэг вручную и контрол конечно же будет считать его новым тэгом (т.к. выбран не из списка), а хотелось бы такую штуку, чтобы после ввода тэга контрол сходил и посмотрел - есть ли такой тэг и если он есть, снял с него метку "новый" + поправил название, если пользователь набрал не основное название тэга.
Ой сложно то как... в общем, в демке все будет ясно (попробуйте набрать например rd и ввод).
Контрол основан на том моем автокомплитере, чтобы не плодить ненужных сущностей. Т.е. для работы ему требуются файлы автокомплитера (css + js) и плагин jquery.autGrowInput.js, подсмотренный мной на stackoverflow и чуть допиленный. Ну и для некоторых браузеров может понадобиться json2.js.
Что получилось
Демо
Ввод тэгов, использующий массив
API
Создание
var ti = $ ( селектор текстового поля ).tagsinput( data, options );
data
URL для загрузки данных либо массив данных
options
дополнительный настройки:
delimiters
строка, содержащая все символы, возможные в качестве разделителя тэгов (по умолчанию , и [enter])
allowNewTags
true - если можно добавлять новые тэги, а не только выбирать их из списка (по умолчанию false)
inputClass
имя класса поля ввода тэгов (по умолчанию tagsinput)
DDClass
имя класса для блока результата, если нужно
DDzIndex
z-index блока результата (по умолчанию 10000)
DDminChars
кол-во символов, после которых идет поиск (по умолчанию 1)
DDdelay
время ожидания в миллисекундах после нажатия клавиши и до попытки поиска (по умолчанию 400)
DDmaxItemsToShow
максимальное кол-во результатов в списке (если -1 - без ограничений, по умолчанию -1)
DDhighlightFirst
true - при появлении результатов, первый из них автоматически подсвечивается (по умолчанию false)
DDwidth
ширина выпадающего списка (0 - рассчитывается автоматически = ширине поля ввода, по умолчанию 0)
DDheight
максимальная высота выпадающего списка, если элементов больше - появляется прокрутка (0 - неограничена, по умолчанию 0)
loadingClass
имя класса поля ввода при ожидании результатов с сервера, если нужно
cacheLength
максимальное число кэшируемых результатов поиска (если 0 - кэширование будет отключено, -1 - бесконечно, по умолчанию 10)
mustMatch
true и поле не выбрано - после потери фокуса оно очистится (по умолчанию false)
extraParams
дополнительные параметры при запросе на сервер (объект имя:значение)
delay
время в миллисекундах, после добавления тэга и до вызова фии проверки тэгов (по умолчанию 400)
msgNotFound
сообщение, показываемое, если ничего не найдено (по умолчанию not found...)
msgServerError
сообщение, показываемое при ошибке сервера (по умолчанию server error...)
msgError
сообщение, показываемое при других ошибках (по умолчанию error...)
checkTags
true - включения встроенной функции проверки тэгов, false - отключение данной функции, или пользовательская функция, по умолчанию false
parseData
функция преобразования входных данных
DDformatItem
функция формирования вида элемента списка
formatItem
функция формирования вида элемента тэга
eventsHandler
функция обработки событий
Формат данных
Данные, пересылаемые как JSON, или объявленные и переданные как массив должны быть в минимально необходимом виде такими:
Если у элемента присутствуют дополнительные поля, кроме минимально необходимых - они сохранятся в нем и их можно будет потом использовать. Необязательный элемент _ti_ говорит о том, как отображать данный тэг. Если new = true - тэг будет отображаться как "новый", если check = true - тэг будет проверен при разрешенной операции проверки тэгов. Если не указать id тэг будет автоматически считаться новым и будет проверен при операции проверки тэгов.
Запрос на поиск идет вида POST со строкой в переменной q, и переменной mode = search, кроме того в переменной items находятся все текущие выбранные тэги в JSON. Элементы, содержащиеся в items при запросе будут в том-же формате, который показан выше. Если у тэга не указан id, то он будет равным null, у всех тэгов будет сформирован элемент _ti_.
Функция преобразования входных данных
function parseData( items ){ ... return items;}
items
входные данные
Функция формирования вида элемента списка
function DDformatItem( container, item, itemNumber, items, itemsLen ){
...
return{
html:(null, строка или DOM объект),- HTML / DOM объект данного элемента списка,
value: строка,- значение, помещаемое при выборе в поле ввода
};}
container
DOM объект в котором будет храниться элемент и его данные
item
данные элемента
itemNumber
порядковый номер элемента в списке
items
данные всех элементов
itemsLen
количество всех элементов
Должна возвращать объект с полями:
html
HTML / DOM данного элемента (или null, если не нужно)
value
значение, помещающееся в поле ввода при выборе данного элемента
Функция формирования вида тэга
function formatItem( container, tag ){
...
return HTML / DOM объект /null данного тэга;}
container
DOM объект в котором будет храниться тэг и его данные
tag
данные тэга
Должна возвращать HTML или DOM данного элемента (или null, если не нужно)
Функция обработки событий
function eventsHandler( event, obj, item ){ ... }
Значения event:
DDover / DDout
наведение / снятие наведения с элемента в списке (obj - DOM элемента, item - данные элемента)
DDselect
выбор элемента в списке (obj - DOM элемента, item - данные элемента)
DDunselect
отмена выбора элемента (obj - null, item - данные элемента)
DDshow / DDhide
показывание / скрытие списка (obj - DOM списка)
DDbeforeRequest
вызывается до того, как пойдет запрос на поиск (obj - options, item - options.extraParams)
checkStart
стандартная процедура проверки тэгов начинает проверку (obj - пришедшие данные из запроса в виде объекта)
checkEnd
стандартная процедура проверки тэгов закончила проверку (obj - пришедшие данные из запроса в виде объекта)
checkError
стандартная процедура проверки тэгов получила на запрос ответ с ошибкой (obj - пришедшие данные из запроса в виде объекта или текста)
deleted
тэг удален (obj - DOM удаленного тэга)
duplicate
при создании тэга выявлено, что это дубль уже существующего элемента, создание отменено (obj - DOM найденного оригинального элемента, item - данные элемента-дубля, еще 2 доп. переменных: newTag и checkTag - признаки для элемента-дубля )
insert
добавление тэга (obj - DOM тэга, item - данные тэга)
add
добавлен тэг из списка (obj - DOM тэга)
addNew
создан новый тэг (obj - DOM тэга)
Методы: получить значение
ti.get( onlyData, idOnly );
onlyData
true - вернет массив, состоящих только из данных выбранных тэгов / false - вернет массив DOM элементов выбранных тэгов
idOnly
true - вернет массив из id выбранных тэгов (если у тэга нет - он не попадет в этот массив), работает только при onlyData = true
Если ни одного тэга не выбрано вернет пустой массив.
Методы: установить значение
ti.set( items, triggerEvent );
items
массив данных тэгов (как описано в формате данных)
triggerEvent
true - для генерации событий изменения (может быть опущен, по умолчанию false)
Если опущены все параметры, контрол просто очищается.
Под "опущено" подразумевается значение undefined или null.
Устанавливает параметры, идущие при ajax-запросе, переписывает options.extraParams, если был задан.
Методы: показать список
ti.showResults( status, timer );
status
true - показать / false - спрятать
timer
true - показать список со стандартной задержкой
Методы: проверить и убрать дубликаты
ti.removeDupes();
Проверить на дубликаты выбранные тэги, и убрать их, если найдены.
Методы: проверить тэги
ti.checkTags();
Запустить проверку тэгов.
Описание логики работы проверки тэгов
Если в checkTags указана своя функция, то она должна быть вида
function( checkedItems, itemsForCheck ){}
checkedItems
список DOM объектов уже проверенных тэгов
itemsForCheck
список DOM объектов тэгов для проверки
В DOM элементе, в аттрибуте data-item хранятся данные тэга ({ id: ..., text: ..., pattern: ..., ... }).
Признаки check и new - это соответственные классы данного DOM элемента. checkedItems + itemsForCheck - это все тэги, которые на данный момент выбраны.
До название тэга добраться можно так (например для первого элемента): itemsForCheck[0].firstChild.textContent (textNode).
Если в checkTags указано true - т.е. включена встроенная функция проверки тэгов, то проверка тэгов будет работать только через ajax.
Перед ajax запросом каждому тэгу будет сгенерировано уникальное ID, которое сохраняется в его DOM, в атрибут data-id.
Затем будет запущен ajax запрос на url с параметрами mode=check, items c данными всех выбранных тэгов (в формате JSON).
В каждое "данное" тэга ставится дополнительный член _ti_:
[{
id: id тэга (отсутствует у нового тэга или null)
text: текст тэга,
pattern: ...
...
_ti_:{
id: сгенерированное ID для данного тэга,"new":true|false,
check:true|false,}},
...
]
Обратно будет ожидаться JSON массив с элементами, которые требуются модифицировать:
[{
id: id тэга (отсуствует у нового тэга или null)
text: текст тэга,
....
_ti_:{
id: ID из запроса, который пришел
"new":true|false,
check:true|false,
del:true|false}},
...
]
Если есть _ti_.id и _ti_.del = true, то тэг будет удален (в этом случае можно передавать в _ti_ только id и del)
Если есть _ti_.id, то тэг с данным id будет модифицирован - все его данные будут заменены пришедшими данными, это же будет с его new / check.
Если нет _ti_.id, то данный тэг будет создан и добавлен в конец выбранных тэгов.
Данные тэга в его DOM
В DOM элементе, в атрибуте data-item хранятся все данные этого тэга ({ id: ..., text: ..., pattern: ..., ... }).
Признаки check и new - это соответственные классы данного DOM элемента, если они = false, то они просто отсутствуют.
Как обращатся к методам?
Либо так:
var ti = $( селектор текстового поля ).tagsinput( ... )
ti.метод(...);
Либо так:
$( селектор текстового поля ).tagsinput().метод(...);
Годная штука, но так как потенциально автор может заявить за использование в коммерческом проекте, то смысла брать нету. На гитхабе есть аналоги с MIT лицензией.
Смысл брать есть.
Во-первых, автор пока никуда ничего не заявил :D
Во-вторых, если Вы хотите применять что-то для коммерческого использования, почему бы не поддержать автора и не заплатить ему?
Тем более автор - не компания и вряд-ли назовет какую-то неподъемную сумму.