Однажды я зашел на rememberthemilk.com и понял, что хочу такое же контекстное autocomplete меню в свой проект. В результате получился небольшой jquery плагин, который хочу презентовать в этом посте. Работает в ie6+, opera, safari, firefox, chrome (тестировал в последних версиях). В кратце расскажу в чем суть «контекстного» меню в RTM-стиле.
Это меню присоединяется к input-элементу, но, в отличие от обычных autocomplete меню, оно «всплывает» не для ввода всего значения элемента, а для какой-то логической части поля ввода. При этом меню позиционируется непосредственно под автодополняемым текстом. Вот как это выглядит:
Лицензия проекта — MIT / beerware.
Скачать библиотеку с примерами можно тут: js-context-autocomplete.googlecode.com/files/js-autocomplete-v5.tar
Последнюю ревизию забираем тут: svn checkout js-context-autocomplete.googlecode.com/svn/trunk js-context-autocomplete-read-only
Кому интересно поучаствовать в проекте — пишите в личку.
Временное online-demo (upd)
Под катом интересные моменты реализации, описание функциональности, примеры, список известных багов и фич для реализации.
1. после выбора пункта меню курсор перемещается в конец всего текста, ожидается перемещение в конец вставленного фрагмента
2. после выбора кликом мышки поле ввода не получает фокус обратно
3. короткое поле для ввода и длинный текст — ошибка позиционирования меню
4. слепая вера в то, что элементы меню — строки (добавить проверку и приведение к строке)
5. autocomplete=«off» не включено по умолчанию (необходимо включить)
6. отсутствует лимит на количество элементов, отображаемых одновременно, для слишком длинных меню
1. ajax-загрузка элементов меню
2. сложный формат элементов меню (эмуляция ), доп. события
3. возможность настройки стиля каждого элемента меню
4. зависимость меню от сторонних данных (в этом же елементе или на странице)
Изначально меня заинтересовало в RTM autocomplete то, каким образом можно получить расстояние в пикселях от начала поля ввода до текущей позиции курсора. Именно это подтолкнуло меня на начало работы. Оказывается, что решить эту проблему можно довольно просто: достаточно вычислить ширину скрытого div'а, заполненного текстом с тем же стилем, что и в меню. К сожалению, это решение порождает баг номер 3, но меня это не особо волнует пока. Возможно кто-то может предложить лучшее решение?
Следующий интересный момент — определение позиции для которой должно всплывать меню. Для «родного» RTM-меню свойственно выпадать сразу после ввода ключевых символов (@ для локации, # для тега,! для приоритета и т.д.), однако не хотелось ограничиваться таким поведением и предоствавить свободу показывать меню для любых случаев: хоть для ввода тэгов через запятую, хоть каких-то более для сложных форматов. Тут на помощь приходят регулярные выражения. Для каждого множества элементов назначается регулярное выражение, с которым должна совпасть введённая строка. Когда строка совпала с регулярным выражением — отображаем меню начиная с первого совпавшего символа в автодополняемой строке. И сразу пример, чтобы понять о чем я.
Пусть мы хотим чтобы в поле для ввода можно было ввести сумму в рублях и статью расхода, куда деньги потрачены. При этом мы хотим, чтобы после окончания ввода цифр появлялось меню. Пишем простейшее регулярное выражение для такого поведения: /^\d+\s+(.*)$/. Теперь когда мы введем число и пробел, всплывет меню.
Усложним ситуацию. Пусть мы уже ввели значение и меню исчезло, но потом передумали, удалили несколько символов чтобы выбрать другой пункт меню. Мы хотим чтобы меню всплыло не рядом с курсором, а от начала слова, котрое мы изменяем. Для этого в нашем регулярном выражении мы «ловим» слово используя такую конструкцию (.*). Кстати, если бы мы хотели, чтобы меню всплывало после первого совпавшего символа слова, очевидно эта конструкция должна была бы выглядеть как (.+). Таким образом достаточно просто можно настраивать сколь угодно сложные способы поведения меню.
Для тех кто любит поломать мозг над регулярными выражениями, задачка: как написать регулярное выражение, которое совпадало бы для тегов, вводимых через запятую. Пока что я решаю эту задачу так:
Есть варианты, как обойтись одним регулярным выражением и удовлетворить требованию, чтобы автодополняемый тэг ловился в первом совпавшем фрагменте?
На всё вышеозначенное ушло совсем немного времени — один вечер (два-три часа). После этого оно работало в FF и как-то странно в Opera, отказывалось слушаться клавиш up-down-enter-esc в msie и webkit. А поделиться с тем что у меня получилось хотелось бы со всеми желающими. Подзуживало еще то, что в rememberthemilk все работает одинаково во всех браузерах. Так в чем же проблема? Тезисно:
1. key events (keypress, keydown) — очень повеселило, как разные браузеры работают с клавиатурными событиями. Помогает это и это (9: JavaScript Events)
2. selectionStart, selectionEnd — msie не знает об этом. Но там же createRange есть.
3. jquery.each оказалось, что этот замечательный метод работает немного по-разному в ie и не ie. Мелочь, но это тема для другого разговора, потому что интересно почему так, но разобраться пока не было шанса.
Code review required ;)
Это меню присоединяется к input-элементу, но, в отличие от обычных autocomplete меню, оно «всплывает» не для ввода всего значения элемента, а для какой-то логической части поля ввода. При этом меню позиционируется непосредственно под автодополняемым текстом. Вот как это выглядит:
Лицензия проекта — MIT / beerware.
Скачать библиотеку с примерами можно тут: js-context-autocomplete.googlecode.com/files/js-autocomplete-v5.tar
Последнюю ревизию забираем тут: svn checkout js-context-autocomplete.googlecode.com/svn/trunk js-context-autocomplete-read-only
Кому интересно поучаствовать в проекте — пишите в личку.
Под катом интересные моменты реализации, описание функциональности, примеры, список известных багов и фич для реализации.
Примеры использования
- var $input = $('#text_input');
- // обычный автокомплит (не контекстный)
- $input.autocomplete(['Питание', 'Бытовые расходы', 'Машина', 'Здоровье', 'Счета']);
-
- // короткий формат записи:
- // принимает объект, в котором ключом может быть либо один символ,
- // тогда соответствующее меню будет появляться после этого символа,
- // либо регулярное выражение вида '^something(match)$' заданное строкой
- // (будет входным параметром для конструктора RegExp)
- $input.autocomplete({
- '^\\d+\\s+(.*)$': categories,
- '^\\d+\\s+.*?\: (.+)$': notes,
- '#': habra_tags, // при наборе символа # показываем хабра-тэги
- '@': places, // при наборе символа @ будет отображаться меню с локациями
- '!': ['1', '2', '3'] // при наборе символа ! будет отображаться меню с приоритетами
- });
-
- // расширенный формат записи:
- // принимает массив объектов
- // в которых обязательными являются члены regex и items
- $input_with_suffix.autocomplete([
- {
- regex: /^\d+[.,]?\d*\s+(.*)$/,
- items: categories,
- suffix: ': ' // этот суффикс будет автоматически добавляться при выборе из меню
- }, {
- regex: /^\d+[.,]?\d*\s+.*?: (.+)$/,
- items: notes
- }
- ]);
* This source code was highlighted with Source Code Highlighter.
Известные баги
1. после выбора пункта меню курсор перемещается в конец всего текста, ожидается перемещение в конец вставленного фрагмента
3. короткое поле для ввода и длинный текст — ошибка позиционирования меню
6. отсутствует лимит на количество элементов, отображаемых одновременно, для слишком длинных меню
Фичи для последующей реализации
1. ajax-загрузка элементов меню
2. сложный формат элементов меню (эмуляция ), доп. события
3. возможность настройки стиля каждого элемента меню
4. зависимость меню от сторонних данных (в этом же елементе или на странице)
Зачем это всё?
Как это работает?
Изначально меня заинтересовало в RTM autocomplete то, каким образом можно получить расстояние в пикселях от начала поля ввода до текущей позиции курсора. Именно это подтолкнуло меня на начало работы. Оказывается, что решить эту проблему можно довольно просто: достаточно вычислить ширину скрытого div'а, заполненного текстом с тем же стилем, что и в меню. К сожалению, это решение порождает баг номер 3, но меня это не особо волнует пока. Возможно кто-то может предложить лучшее решение?
Когда всплываем?
Следующий интересный момент — определение позиции для которой должно всплывать меню. Для «родного» RTM-меню свойственно выпадать сразу после ввода ключевых символов (@ для локации, # для тега,! для приоритета и т.д.), однако не хотелось ограничиваться таким поведением и предоствавить свободу показывать меню для любых случаев: хоть для ввода тэгов через запятую, хоть каких-то более для сложных форматов. Тут на помощь приходят регулярные выражения. Для каждого множества элементов назначается регулярное выражение, с которым должна совпасть введённая строка. Когда строка совпала с регулярным выражением — отображаем меню начиная с первого совпавшего символа в автодополняемой строке. И сразу пример, чтобы понять о чем я.
Пусть мы хотим чтобы в поле для ввода можно было ввести сумму в рублях и статью расхода, куда деньги потрачены. При этом мы хотим, чтобы после окончания ввода цифр появлялось меню. Пишем простейшее регулярное выражение для такого поведения: /^\d+\s+(.*)$/. Теперь когда мы введем число и пробел, всплывет меню.
Усложним ситуацию. Пусть мы уже ввели значение и меню исчезло, но потом передумали, удалили несколько символов чтобы выбрать другой пункт меню. Мы хотим чтобы меню всплыло не рядом с курсором, а от начала слова, котрое мы изменяем. Для этого в нашем регулярном выражении мы «ловим» слово используя такую конструкцию (.*). Кстати, если бы мы хотели, чтобы меню всплывало после первого совпавшего символа слова, очевидно эта конструкция должна была бы выглядеть как (.+). Таким образом достаточно просто можно настраивать сколь угодно сложные способы поведения меню.
Для тех кто любит поломать мозг над регулярными выражениями, задачка: как написать регулярное выражение, которое совпадало бы для тегов, вводимых через запятую. Пока что я решаю эту задачу так:
- $input_just_for_tags.autocomplete([
- {
- // первый тег (с начала строки)
- regex: /^([^,]+)$/,
- items: habra_tags
- },
- {
- // не первый тег (то что до последней запятой перед курсором)
- regex: /^.*,\s+([^,]+)$/,
- items: habra_tags
- }
- ]);
* This source code was highlighted with Source Code Highlighter.
Есть варианты, как обойтись одним регулярным выражением и удовлетворить требованию, чтобы автодополняемый тэг ловился в первом совпавшем фрагменте?
Рутина
На всё вышеозначенное ушло совсем немного времени — один вечер (два-три часа). После этого оно работало в FF и как-то странно в Opera, отказывалось слушаться клавиш up-down-enter-esc в msie и webkit. А поделиться с тем что у меня получилось хотелось бы со всеми желающими. Подзуживало еще то, что в rememberthemilk все работает одинаково во всех браузерах. Так в чем же проблема? Тезисно:
1. key events (keypress, keydown) — очень повеселило, как разные браузеры работают с клавиатурными событиями. Помогает это и это (9: JavaScript Events)
2. selectionStart, selectionEnd — msie не знает об этом. Но там же createRange есть.
3. jquery.each оказалось, что этот замечательный метод работает немного по-разному в ie и не ie. Мелочь, но это тема для другого разговора, потому что интересно почему так, но разобраться пока не было шанса.
Вместо резюме
Code review required ;)