При интеграции библиотеки Awesomplete с удалёнными источниками данных наиболее распространённым форматом обмена становится JSON. Сервер возвращает структурированный ответ, содержащий массив строк, объектов или сложных вложенных структур, а клиентская часть преобразует эти данные в список подсказок автодополнения.
Awesomplete не выполняет автоматическую обработку JSON-ответов.
Полученные данные необходимо самостоятельно разобрать, преобразовать и
передать в свойство list.
Базовая схема работы выглядит следующим образом:
Наиболее простой вариант ответа сервера:
[
"JavaScript",
"Java",
"Python",
"PHP",
"Rust"
]
Такой формат идеально подходит для Awesomplete, поскольку библиотека умеет работать напрямую с массивами строк.
fetch("/languages")
.then(response => response.json())
.then(data => {
awesomplete.list = data;
});
Метод:
response.json()
автоматически преобразует JSON-строку:
["JavaScript","Java"]
в полноценный JavaScript-массив:
["JavaScript", "Java"]
После этого массив передаётся в:
awesomplete.list
Часто сервер возвращает не массив напрямую, а объект с дополнительными полями:
{
"success": true,
"items": [
"JavaScript",
"Java",
"Python"
]
}
В этом случае необходимо извлечь нужное поле.
fetch("/languages")
.then(response => response.json())
.then(data => {
awesomplete.list = data.items;
});
Подобная структура позволяет серверу передавать:
Например:
{
"success": true,
"count": 3,
"items": [
"JavaScript",
"Java",
"Python"
]
}
В реальных приложениях сервер обычно возвращает не строки, а объекты.
Пример:
[
{
"id": 1,
"name": "JavaScript"
},
{
"id": 2,
"name": "Python"
}
]
Awesomplete не знает, какое поле отображать, поэтому данные нужно преобразовать.
Самый простой способ — извлечь одно поле.
fetch("/languages")
.then(response => response.json())
.then(data => {
const list = data.map(item => item.name);
awesomplete.list = list;
});
Из:
[
{ id: 1, name: "JavaScript" },
{ id: 2, name: "Python" }
]
получается:
[
"JavaScript",
"Python"
]
Иногда требуется не только показать текст, но и сохранить дополнительную информацию:
В таком случае строки недостаточно.
Awesomplete поддерживает следующий формат:
[
{
label: "JavaScript",
value: "1"
},
{
label: "Python",
value: "2"
}
]
fetch("/languages")
.then(response => response.json())
.then(data => {
const list = data.map(item => ({
label: item.name,
value: item.id
}));
awesomplete.list = list;
});
Отвечает за отображаемый текст.
label: "JavaScript"
Передаётся в input после выбора элемента.
value: "1"
Если в value хранится идентификатор, после выбора
пользователь увидит число:
<input value="1">
Во многих случаях это нежелательно.
new Awesomplete(input, {
replace: function(selected) {
this.input.value = selected.label;
}
});
Серверные API часто возвращают вложенные структуры.
Пример:
{
"data": {
"results": [
{
"id": 1,
"attributes": {
"title": "JavaScript"
}
},
{
"id": 2,
"attributes": {
"title": "Python"
}
}
]
}
}
fetch("/api/search")
.then(response => response.json())
.then(data => {
const results = data.data.results;
const list = results.map(item => ({
label: item.attributes.title,
value: item.id
}));
awesomplete.list = list;
});
При работе с удалённым API структура ответа может изменяться. Отсутствие нужного поля вызывает ошибки.
Проблемный код:
data.results.map(...)
Если results отсутствует:
Cannot read properties of undefined
fetch("/api/search")
.then(response => response.json())
.then(data => {
if (!data.results) {
return;
}
awesomplete.list = data.results;
});
Современный JavaScript позволяет безопасно обращаться к вложенным свойствам.
const results = data?.data?.results;
fetch("/api/search")
.then(response => response.json())
.then(data => {
const results = data?.data?.results;
if (!results) {
return;
}
awesomplete.list = results;
});
Иногда сервер возвращает не массив, а объект или строку.
fetch("/api/search")
.then(response => response.json())
.then(data => {
if (!Array.isArray(data)) {
return;
}
awesomplete.list = data;
});
Сервер может вернуть пустой массив:
[]
Awesomplete корректно обрабатывает такую ситуацию.
awesomplete.list = [];
Список подсказок просто не отображается.
Перед передачей данных в Awesomplete их можно фильтровать.
[
{
"name": "JavaScript",
"active": true
},
{
"name": "Old Language",
"active": false
}
]
fetch("/languages")
.then(response => response.json())
.then(data => {
const filtered = data
.filter(item => item.active)
.map(item => item.name);
awesomplete.list = filtered;
});
Awesomplete может отображать данные в том порядке, в котором они были получены.
const sorted = data.sort((a, b) => {
return a.name.localeCompare(b.name);
});
fetch("/languages")
.then(response => response.json())
.then(data => {
const sorted = data
.sort((a, b) => a.name.localeCompare(b.name))
.map(item => item.name);
awesomplete.list = sorted;
});
Некоторые API возвращают тысячи записей.
Перед передачей данных в Awesomplete желательно ограничить размер списка.
const limited = data.slice(0, 10);
fetch("/search")
.then(response => response.json())
.then(data => {
const list = data
.slice(0, 10)
.map(item => item.name);
awesomplete.list = list;
});
Иногда требуется сложная логика обработки.
const list = data.reduce((result, item) => {
if (item.active) {
result.push({
label: item.name,
value: item.id
});
}
return result;
}, []);
Ошибка может возникнуть:
fetch("/search")
.then(response => response.json())
.then(data => {
awesomplete.list = data;
})
.catch(error => {
console.error(error);
awesomplete.list = [];
});
Если сервер возвращает:
Internal Server Error
вместо JSON, метод:
response.json()
выбросит исключение.
Поэтому обработка ошибок обязательна.
Даже при ошибке fetch() не всегда вызывает
catch().
Например:
404 Not Found
не считается сетевой ошибкой.
fetch("/search")
.then(response => {
if (!response.ok) {
throw new Error("Ошибка сервера");
}
return response.json();
})
.then(data => {
awesomplete.list = data;
})
.catch(error => {
console.error(error);
});
Современный синтаксис делает код более читаемым.
async function loadSuggestions(query) {
try {
const response = await fetch("/search?q=" + query);
if (!response.ok) {
throw new Error("Ошибка запроса");
}
const data = await response.json();
awesomplete.list = data;
} catch (error) {
console.error(error);
}
}
JSON-ответы могут содержать:
Пример:
[
"Москва",
"Караганда",
"Алматы"
]
Awesomplete корректно работает с Unicode при условии правильной кодировки сервера:
Content-Type: application/json; charset=utf-8
Иногда API возвращает данные с лишними пробелами.
const list = data.map(item => item.trim());
const list = data.map(item => item.toLowerCase());
API может возвращать повторяющиеся значения.
const unique = [...new Set(data)];
fetch("/search")
.then(response => response.json())
.then(data => {
const unique = [...new Set(data)];
awesomplete.list = unique;
});
Иногда подсказка должна содержать несколько значений.
[
{
"city": "Караганда",
"country": "Казахстан"
},
{
"city": "Москва",
"country": "Россия"
}
]
const list = data.map(item => {
return `${item.city} (${item.country})`;
});
JSON можно отображать в виде сложной HTML-разметки.
new Awesomplete(input, {
item: function(text, input) {
const element = document.createElement("li");
element.innerHTML = `
<strong>${text.label}</strong>
<small>${text.country}</small>
`;
return element;
}
});
const list = data.map(item => ({
label: item.city,
country: item.country
}));
Некоторые API отвечают медленно. Если пользователь быстро вводит текст, запросы начинают накладываться друг на друга.
Пользователь ввёл:
jav
а затем:
javascript
Первый запрос может завершиться позже второго и перезаписать актуальные данные.
let currentQuery = "";
input.addEventListener("input", async function() {
currentQuery = input.value;
const query = currentQuery;
const response = await fetch("/search?q=" + query);
const data = await response.json();
if (query !== currentQuery) {
return;
}
awesomplete.list = data;
});
Повторные запросы можно сохранять локально.
const cache = {};
async function load(query) {
if (cache[query]) {
awesomplete.list = cache[query];
return;
}
const response = await fetch("/search?q=" + query);
const data = await response.json();
cache[query] = data;
awesomplete.list = data;
}
Некоторые API всегда возвращают JSON даже при ошибках.
Пример:
{
"success": false,
"message": "Access denied"
}
fetch("/search")
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error(data.message);
return;
}
awesomplete.list = data.items;
});
JSON желательно приводить к единому формату до передачи в Awesomplete.
Оптимальная последовательность обработки:
awesomplete.list.async function loadSuggestions(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Ошибка сервера");
}
const data = await response.json();
if (!Array.isArray(data.items)) {
return [];
}
return data.items
.filter(item => item.active)
.sort((a, b) => {
return a.name.localeCompare(b.name);
})
.slice(0, 10)
.map(item => ({
label: item.name,
value: item.id
}));
} catch (error) {
console.error(error);
return [];
}
}