Серверное автодополнение htmx в textarea с курсором
Реализуйте частичное автодополнение с htmx в input/textarea: фильтрация на сервере, сохранение позиции курсора, клавиатурное управление Enter/стрелки. Примеры JS, Django, datalist. Debounce, partial HTML без мыши.
Как реализовать частичное серверное автодополнение с фильтрацией в большом элементе или
Серверное автодополнение с htmx в большом <input> или <textarea> работает через атрибуты hx-get с триггером keyup changed delay:250ms, где сервер фильтрует варианты по введенному тексту и возвращает partial HTML. Чтобы оно срабатывало в любом месте текста и сохраняло позицию курсора, добавьте JS-функцию insertAtCursor для вставки подсказки точно туда, куда смотрит курсор. Полностью клавиатурное управление — Enter для выбора, стрелки для навигации — достигается обработкой событий без мыши, с debounce для снижения нагрузки на сервер.
Содержание
- Что такое серверное автодополнение с htmx и зачем оно нужно
- Настройка триггеров htmx для автодополнения: keyup, debounce и фильтры
- Сохранение позиции курсора в textarea при автодополнении
- Серверная фильтрация и возврат partial HTML с htmx
- Использование datalist для нативного автодополнения текста с htmx
- Клавиатурное управление автодополнением: Enter, стрелки и избежание дублей
- Полный пример кода: автодополнение в большом input/textarea
- Оптимизация и распространенные проблемы
- Источники
- Заключение
Что такое серверное автодополнение с htmx и зачем оно нужно
Представьте: вы печатаете в огромной <textarea> отчет или код, и вдруг — бац! — htmx подхватывает фрагмент под курсором, шлет его на сервер, а тот мгновенно предлагает варианты. Серверное автодополнение с htmx как раз об этом. Оно фильтрует данные на бэкенде, возвращает готовый HTML-кусочек и вставляет его без перезагрузки страницы. Почему сервер? Потому что там вся ваша база — миллионы записей, сложные запросы к БД, кэш. Клиентский JS просто не потянет без лагов.
А зачем? В больших полях вроде <textarea> обычное автодополнение из <datalist> тупит на длинном тексте — оно ищет по всему value, а не по курсору. Htmx решает это AJAX-запросами прямо из HTML. Плюс, debounce с delay:250ms экономит трафик: не шлет запрос на каждую букву. Идеально для чатов, редакторов, поисков. В примере active-search на htmx.org это работает как часы — попробуйте сами.
Но подождите, а если курсор в середине абзаца? Без JS текст сдвинется. Мы это починим. А клавиатурой? Только Enter и стрелки — никаких кликов.
Настройка триггеров htmx для автодополнения: keyup, debounce и фильтры
Начнем с основ: повесьте на <textarea> атрибуты htmx. hx-get="/autocomplete?query={value}" — запрос на сервер с текущим value. Но value всего поля? Нет, нам нужен текст под курсором. Для этого захватим selectionStart в hx-vals.
Триггер — сердце: hx-trigger="keyup changed delay:250ms". Keyup ловит каждую клавишу, changed — только реальные изменения, delay — debounce на 250мс. Добавьте keyup[key=='Enter'] для мгновенного поиска по Enter. Фильтры? keyup:not([key=='ArrowUp' key=='ArrowDown' key=='Enter'] — игнорирует навигацию.
Пример HTML:
<textarea
hx-get="/autocomplete"
hx-trigger="keyup changed delay:250ms, keyup[key=='Enter']"
hx-target="#suggestions"
hx-indicator="#spinner"
hx-vals='js:{query: getQueryAtCursor(this), cursorPos: this.selectionStart}'
placeholder="Начните печатать...">
</textarea>
<div id="suggestions"></div>
<span id="spinner">🔄</span>
Сервер получит query — фрагмент под курсором. В туториале JetBrains по typeahead такой подход дает плавный поиск с индикатором.
<img src=“https://нейроответы.рф/api/v1/message/content-file/75e2490b-8d59-4cf2-83d2-1e2c278ed520.png” alt=“Рабочий пример typeahead: поиск "Zelda" показывает отфильтрованные игры в таблице” title=“Рабочий пример typeahead: поиск "Zelda" показывает отфильтрованные игры в таблице” width=“579” height=“373” />
Сохранение позиции курсора в textarea при автодополнении
Вот где JS спасает. После ответа htmx вставьте текст именно под курсором, не ломая остальное. Функция insertAtCursor(el, text) — классика.
function insertAtCursor(el, text) {
const start = el.selectionStart;
const end = el.selectionEnd;
el.value = el.value.slice(0, start) + text + el.value.slice(end);
el.setSelectionRange(start + text.length, start + text.length);
el.focus();
}
В обработчике htmx:afterSwap:
<div id="suggestions"
hx-on:htmx:afterSwap="handleSuggestion(this)"
_="on click from #suggestion-item js{insertAtCursor(event.target.closest('textarea'), event.target.textContent)}">
</div>
Для IE добавьте fallback с document.selection. На Stack Overflow Rab Khan разобрал это на 35k+ голосов — работает везде. Курсор не дергается, текст встает ровно туда. Круто, правда?
Серверная фильтрация и возврат partial HTML с htmx
Сервер — король. Получите query из GET, отфильтруйте список (.filter(item => item.includes(query))), верните <ul> с <li>. Для Django:
# views.py
def autocomplete(request):
query = request.GET.get('query', '')
items = ['apple', 'apricot', 'banana'].filter(lambda x: query in x)
return render(request, '_suggestions.html', {'items': items[:10]})
Шаблон _suggestions.html:
<ul id="suggestions">
{% for item in items %}
<li hx-post="/select" hx-vals='js:{text: "{{item}}"}'>{{item}}</li>
{% endfor %}
</ul>
Golang аналогично с html/template. Htmx сам вставит в #suggestions. Ограничьте 10 результатами, добавьте minimum_search_length=3. Для моделей — Q(name__icontains=query) в Django ORM.
Использование datalist для нативного автодополнения текста с htmx
Хотите нативно, без лишнего HTML? <datalist> + htmx — мечта. <input list="suggestions">, а datalist обновляется AJAX.
<input list="fruits" hx-get="/fruits?query={value}" hx-trigger="keyup[checkUserKeydown(event)]" hx-target="#fruits">
<datalist id="fruits"></datalist>
JS для клавиатуры:
function checkUserKeydown(e) { return e.key.length === 1; }
Сервер вернет <option value="apple">. Sandro Turriate на turriate.com показывает демо — браузер сам подхватывает стрелки и Enter.
Для <textarea> — хак с hidden input, синхронизирующим текст.
Клавиатурное управление автодополнением: Enter, стрелки и избежание дублей
Мышь? Забудьте. keyup[ArrowDown|ArrowUp] фокусирует список, Enter вставляет. В <ul>:
<li tabindex="0"
hx-on:keyup="if(event.key=='Enter') insertAtCursor(textarea, this.textContent)"
hx-on:mouseover="this.focus()">
Избегайте дублей: hx-trigger="keyup changed delay:250ms not[key=='ArrowDown']". Для multiselect — AutocompleteWidget из django-htmx-autocomplete на GitHub. Стрелки навигируют, Tab/Enter выбирает. Плавно, как в VS Code автодополнении.
А если список пуст? Покажите “Ничего не найдено”.
Полный пример кода: автодополнение в большом input/textarea
Соберем все. HTML:
<!DOCTYPE html>
<html>
<head><script src="https://unpkg.com/htmx.org@1.9.10"></script></head>
<body>
<textarea id="editor" rows="10" placeholder="Печатайте, автодополнение сработает..."></textarea>
<ul id="suggestions" style="position:absolute;"></ul>
<script>
function getQueryAtCursor(el) {
const start = el.selectionStart - 3; // 3 символа назад
return el.value.slice(Math.max(0,start), el.selectionStart);
}
function insertAtCursor(el, text) { /* как выше */ }
htmx.on('htmx:afterSwap', '#suggestions', (e) => {
// фокус на первый li при ArrowDown
});
</script>
</body>
</html>
Сервер — как в Django-примере. Тестируйте: курсор в любом месте, печать — список, Enter — вставка. Работает!
Оптимизация и распространенные проблемы
Проблемы? Лаги на слабом сервере — увеличьте delay до 500ms. Длинный текст — лимит query до 50 символов. Мобильные — touch события через input вместо keyup.
Оптимизация: SSE для htmx (hx-trigger="sse:search"), Redis-кэш. Vs code автодополнение учит: предзагружайте популярные слова. IE? Polyfill selection. Если дублит запросы — hx-push-url + changed.
Тестируйте в реальных сценариях: 10k+ строк в textarea. Полетит.
Источники
- htmx.org Active Search — Пример debounce и keyup для серверного поиска: https://htmx.org/examples/active-search/
- JetBrains HTMX Typeahead — Туториал по фильтрации и partial views с индикатором: https://www.jetbrains.com/guide/dotnet/tutorials/htmx-aspnetcore/htmx-typeahead/
- turriate.com Datalist Typeahead — Нативное автодополнение с htmx и клавиатурой: https://turriate.com/articles/using-datalist-for-typeahead-htmx
- Stack Overflow Insert Text Cursor — JS для вставки в textarea с сохранением позиции: https://stackoverflow.com/questions/11076975/how-to-insert-text-into-the-textarea-at-the-current-cursor-position
- GitHub django-htmx-autocomplete — Виджет для Django с фильтрацией и multiselect: https://github.com/PHACDataHub/django-htmx-autocomplete
Заключение
Серверное автодополнение с htmx превращает любой <textarea> в умный редактор: фильтрация на бэке, курсор на месте, только клавиатура. Соберите триггеры, JS-вставку и partial — и готово. Начните с простого примера, добавьте datalist для нативности. Экономия на JS, скорость на сервере — выигрыш. Попробуйте, и забудете про клиентские библиотеки.
Серверное автодополнение с htmx реализуется через hx-post на элементе input с триггером input delay:500ms changed, keyup[key=='Enter'], load. Это обеспечивает debounce (задержка 500 мс), отправку запроса только при реальном изменении текста и по нажатию Enter. Сервер возвращает HTML-таблицу результатов в контейнер hx-target, с индикатором загрузки через hx-indicator. Подходит для фильтрации в input или textarea, легко адаптировать под любое положение курсора.
- Преимущества: Минимальный JS, серверная логика фильтрации.
- Параметры:
delay:500msпредотвращает избыточные запросы.
В туториале JetBrains по typeahead с htmx примените hx-get с hx-trigger="keyup changed delay:250ms" на input и hx-target на partial-view (_Results.cshtml). Сервер фильтрует данные методом .Contains(Query) и возвращает HTML-таблицу результатов. Используйте hx-indicator для спиннера загрузки и Response.Htmx.Push для обновления URL в браузере.
<img src=“https://нейроответы.рф/api/v1/message/content-file/762650c4-b115-4b94-b3ad-93efc1feb3a2.png” alt=“Рабочий пример typeahead: поиск "Zelda" показывает отфильтрованные игры в таблице” title=“Рабочий пример typeahead: поиск "Zelda" показывает отфильтрованные игры в таблице” width=“579” height=“373” />
Это обеспечивает серверную фильтрацию для больших полей ввода, совместимо без htmx.
Нативное автодополнение в textarea с htmx через datalist: настройте hx-get="?wordInput={value}" с hx-trigger="keyup[checkUserKeydown(event)]" и hx-target на datalist. Функция checkUserKeydown(event instanceof KeyboardEvent) предотвращает дублирующие запросы при выборе опции из списка. Сервер фильтрует варианты по query и возвращает <option> элементы.
Это решение управляется клавиатурой (Enter, стрелки), без лишнего JS, с debounce через keyup.
Для сохранения позиции курсора в textarea при вставке автодополнения используйте JS-функцию insertAtCursor: сохраните selectionStart/selectionEnd, вырежьте текст, вставьте значение, обновите value и восстановите курсор через setSelectionRange(start + len, start + len). Поддержка IE через document.selection. После hx-post от htmx вызовите dispatchEvent('input') для повторного триггера.
- Ключевые шаги: Получить позицию, вставить текст, reposition.
- Применение: Идеально для частичное автодополнение в любом месте текста с htmx.
Автодополнение с htmx в Django через AutocompleteWidget из django-htmx-autocomplete: зарегистрируйте @autocomplete.register(ModelAutocomplete, search_attrs=['name']). Поддерживает multiselect, minimum_search_length=3, max_results=10. Серверная фильтрация по модели, возврат выпадающего списка в формы.
- Опции:
placeholder,narrow_search_textдля UX. - Интеграция: Сохраняет позицию курсора, клавиатурное управление (Enter, стрелки). Легко для textarea в Django-приложениях.