AJAX – технология, которая появилась достаточно давно и успешно используется до сих пор для загрузки или отправки данных на сервер без перезагрузки текущей страницы. В этой статье мы рассмотрим, каким образом мы можем использовать AJAX в нативном (обычном, или ванильном) JavaScript для загрузки JSON-данных. Загрузку JSON с помощью jQuery вы можете прочитать в отдельной статье.
Подготовка данных для загрузки. JSON-файл с товарами
Формат JSON применяется очень часто для форматирования и передачи данных. Если мы говорим об однотипных данных, то обычно они в JSON представлены в виде массива объектов с одинаковыми полями. Мы будем загружать данные о детских игрушках. Каждый объект-игрушка будет иметь такие поля:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
[
{
“id”:1,
“name”:“Твой собственный Лизун”,
“reviews”: 4,
“stars”: 4,
“regular_price”: 720,
“sale_price”: 459,
“img”: “images/canal_toys.jpg”,
“descr”: “Создайте массу Слими-Лизун своими руками вместе с набором для развлечений Slime Твой собственный Лизун bonus pack от французского производителя CanalToys! Все гениальное просто! Вам не нужно бежать в аптеку или хозяйственный магазин, в данном наборе есть все необходимое. Положи в контейнер пудру слими, добавь блестки, добавь воды до ограничительной линии, потряси около 30 секунд, оставь на 5 минут… и ВУАЛЯ – СлимиЛизун готов!nВ наборе 3 пастельных цвета rainbow и 3 мерцающих цвета Cosmic. Состав набора: 6 пакетиков с пудрой слими-лизун, 6 пакетиков с блестками, 6 контейнеров для смешивания.”
},{
“id”: 2,
“name”:“3D-ручка 3Doodler 48 стержней”,
“reviews”: 4,
“stars”: 3,
“regular_price”: 1499,
“sale_price”: 1349,
“img”: “images/3_doodler.jpg”,
“descr”: “Ручка ломает границы стереотипного мышления, и позволяет выходить за рамки воображения. Это не просто современная игрушка, это отличная возможность расширить детский кругозор, воплощая любые фантазии в реальность.nЭргономичный корпус разработан специально для маленьких детских ручек. Чтобы ничего не отвлекало юного творца от увлекательного процесса 3D — моделирования.”
}
]
|
Вы можете посмотреть на JSON-файл в отдельной вкладке.
Разметка html-файла
Разметка html-файла будет минималистичной. Мы будем загружать информацию о товарах в <div class="row toys-container">
, поэтому внутри он будет пустым. До него мы разместим заголовок страницы в теге h1
и пустой <div class="loader text-center">
для предзагрузчика.
Для того чтобы минимизировать написание стилей, используем css-фреймворк Bootstrap 4, а точнее – стилизацию Bootstrap в виде Bootswatch, тема Cosmo. Из справки Bootstrap 4 возьмем разметку модального окна, немного ее изменив: изменим текст и добавим id. Модальное окно у нас разместиться ниже <div class="container my-5">
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
<!DOCTYPE html>
<html lang=“ru”>
<head>
<meta charset=“UTF-8”>
<title>Toys in Ajax</title>
<!— https://www.bootstrapcdn.com/bootswatch/–>
<link rel=“stylesheet” href=“https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/cosmo/bootstrap.min.css”>
<link rel=“stylesheet” href=“css/style.css”>
</head>
<body>
<div class=“container my-5”>
<h1 class=“text-center”>Детские игрушки</h1>
<div class=“loader text-center”></div>
<div class=“row toys-container”></div>
</div>
<div class=“modal fade” id=“productsModal” tabindex=“-1” role=“dialog”>
<div class=“modal-dialog modal-dialog-centered” role=“document”>
<div class=“modal-content”>
<div class=“modal-header”>
<h5 class=“modal-title”>Информация о товаре</h5>
<button type=“button” class=“close” data–dismiss=“modal” aria–label=“Close”>×</button>
</div>
<div class=“modal-body”>
</div>
<div class=“modal-footer”>
<button type=“button” class=“btn btn-success”>Купить</button>
<button type=“button” class=“btn btn-secondary” data–dismiss=“modal”>Закрыть</button>
</div>
</div>
</div>
</div>
<script src=“js/get_toys.js”></script>
</body>
</html>
|
Перед закрывающимся тегом </body>
разместим скриптовые теги.
Дополнительные css-стили
Добавим ряд css-стилей для созданных нами классов и частично допишем/перепишем стандартные бутстраповские стили.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
.img-holder {
height: 150px;
overflow: hidden;
padding: 10px;
margin-bottom: 10px;
}
.img-holder:hover img {transition: transform .5s;}
.img-holder:hover img { transform: scale(1.15); }
.info {
display: flex;
justify-content: space-between;
}
.star, .star-outline {
width: 10px;
height: 10px;
display: inline-block;
background-image: url(../images/star.png);
background-size: 100%;
margin-left: 3px;
}
.star-outline {
background-image: url(../images/star-outline.png);
}
.card-title {font-weight: bold; margin-top: 10px;}
.modal {background-color: rgba(0, 0, 0, 0.65);}
.modal-dialog {max-width: 700px;}
.regular-price, .sale-price {font-size: 20px; color: #f00;}
.old-price {text-decoration: line-through; color: #aaa; font-size: 16px;}
.review { border-bottom: 1px solid #c3c3c3; margin-bottom: 10px;}
.review:last-of-type { border-bottom: none;}
.author {font-weight: bold;
border-bottom: 1px solid #c3c3c3;
padding: 0 0 10px 0;
}
.review-text {padding: 10px 0;}
.plus strong{color: #53c375;}
.minus strong{color: #f00;}
|
JavaScript код
В JS-коде нам нужно определиться с переменными, которые будут отвечать за определенные элементы в теле страницы и, в том числе, в модальном окне. Многие строки имеют комментарии, поэтому вам нужно внимательно прочитать, что происходит в каждой функции.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
let toysRow = document.querySelector(‘.toys-container’), //контейнер для блоков с игрушками
loadingBlock = document.querySelector(‘.loader’);//контейнер для предзагрузчика
let productsModal = document.getElementById(‘productsModal’),//все модальное окно
modalBody = productsModal.querySelector(‘.modal-body’),//основной текст в модальном окне
modalTitle = productsModal.querySelector(‘.modal-title’),//заголовок модального окна
closeBtns = document.querySelectorAll(‘[data-dismiss=”modal”]’);//кнопки для закрытия модального окна вверху и внизу
let loadImg = new Image();// для загрузки картинки-прелоадера
loadImg.src = ‘images/loader.webp’;
let xhr = new XMLHttpRequest();//для формирования AJAX-запроса
xhr.open(‘GET’, ‘js/products.json’, true);// загружаем json-файл
loadingBlock.append(loadImg);//отображаем предзагрузчик
xhr.responseType = ‘json’;//говорим браузеру о том, какой формат файла будем получать
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
// console.log(xhr.response);//данные в виде массива объектов
showProducts(xhr.response);
}
loadImg.remove();
}
};
xhr.send(null); //отсылаем AJAX-запрос
xhr.onerror = function () {
alert(`Ошибка соединения`);
};
function showProducts(productData) {
let productsStr = ”; //строка для формирования разметки
for (obj of productData) {
//console.log(obj.name);
let starDiv = ‘<div class=”rating”>’; //формируем блок со звездочками
for (let i = 0; i < 5; i++) {
let star = document.createElement(‘span’);
starDiv+= i < obj.stars ? ‘<span class=”star”></span>’:‘<span class=”star-outline”></span>’;
}
const isSale = obj.sale_price? `<span class=“sale-price”>${obj.sale_price} грн</span>`:”; // есть ли цена распродажи
starDiv+=‘</div>’;
productsStr += `<div class=“col-lg-3 col-md-6 my-3”>
<div class=“card”>
<div class=“img-holder”>
<img class=“img-fluid” src=“${obj.img}” alt=“${obj.name}”>
</div>
<div class=“card-body”>
${starDiv}
<div class=“card-title”>${obj.name}</div>
<div class=”prices my-3″><span class=”regular-price${isSale ? ‘ old-price’:”}”>${obj.regular_price} грн</span> ${isSale}</div>
<p class=“card-text d-none”>${obj.descr.replace(/n/g, ‘<br>’)}</p>
<a href=“#productsModal” onclick=“openModal(event)” class=“btn btn-primary” data-id=“${obj.id}”>Подробнее</a>
</div>
</div>
</div>`;
}
toysRow.innerHTML = productsStr;// вставляем разметку в контейнер для нее
}
function openModal (e) { //открываем модальное окно
e.preventDefault();
let link = e.target;
productsModal.style.display = ‘block’;
productsModal.classList.add(‘show’);
document.body.classList.add(‘modal-open’);
modalBody.innerHTML = link.closest(‘.card’).innerHTML;//вставляем в модальное окно весь текст из ссылки
let imgHolder = modalBody.querySelector(‘.img-holder’);
imgHolder.classList.remove(‘img-holder’);//убираем ограничение по высоте картинки
imgHolder.classList.add(‘text-center’);
modalBody.querySelector(‘.card-text’).classList.remove(‘d-none’);//отображаем текст спрятанного абзаца
modalBody.querySelector(‘.btn’).remove();//удаляем из разметки ненужную кнопку “Подробнее”
modalTitle.innerHTML = “Информация о товаре <strong>”+modalBody.querySelector(‘.card-title’).textContent+“<strong>”;
}
closeBtns.forEach(btn => btn.addEventListener(‘click’, closeModal));
document.querySelector(‘.modal’).addEventListener(‘click’, closeModal);
function closeModal(e) { //закрываем модальное окно
if(e.target!== e.currentTarget) return; //если щелкаем не по кнопкам или по фону
productsModal.style.display = ‘none’;
productsModal.classList.remove(‘show’);
document.body.classList.remove(‘modal-open’);
}
|
Если убрать комментарий в строке 19, то вы увидите в консоли, что в переменную попал массив объектов с определенным набором полей, которые мы используем для построения разметки в функции showProducts()
.
Предзагрузчик вы увидите в течение очень небольшого количества времени и только при медленном Интернет-соединении типа 2G или 3G, которые можно эмулировать на вкладке Network в браузере Chrome. При высокой скорости Интернета вы его даже не заметите, т.к. размер JSON-файла невелик, и загрузка выполняется очень быстро.
Давайте посмотрим пример в действии (открыть в новой вкладке). Щелкните на любой из кнопок “Подробнее”, чтобы посмотреть содержимое модального окна.
Добавляем загрузку файла с комментариями
Довольно часто для товаров существуют комментарии или отзывы. Мы видоизменив наш скрипт, добавив загрузку JSON-файла с отзывами в таком виде:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
[
{
“id”: 1,
“product_id”: 1,
“stars”: 1,
“author”: “Iван Головко”,
“text”: “Занадто дорого для набору з такими “сюрпризами””,
“plus”: “Це лизун, на цьому все”,
“minus”: “1. Два з шести лизунів мали погану форму.n 2. Блискітки в наборі зайві. Дівчинка погралась, а потім ці самі блискітки були всюди і погано відмивалися з рук.”
},
{
“id”: 2,
“product_id”: 1,
“stars”: 4,
“author”: “Ирина Воронова”,
“text”: “Купила креснице на подарок. Так вот, ребенок в восторге. Лепила весь вечер. Видно, что очень понравилось. Нет резкого химозного запаха. Красивые и яркие цвета. Вообщем, отличный набор. Цена по акции просто замечательная. О покупке не пожалеете.”,
“plus”: “Классный набор. Ребенку понравился.”,
“minus”: ” Нет”
}
]
|
Полное содержание JSON-файла с отзывами можно посмотреть по ссылке.
В html-разметке мы поменяем только ссылку на js-файл в атрибуте src
тега <script>
, либо вы можете заменить содержимое вашего текущего файла.
JavaScript-код для отображения товаров с отзывами в модальном окне
Нам придется упаковать код AJAX-запроса на сервер в функцию, для того чтобы не писать дважды подобный код. Плюс нужно обработать загрузку нового файла при клике на ссылке с количеством отзывов. Функция showProducts()
из предыдущего скрипта тоже несколько поменяется с учетом информации об отзывах.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
let toysRow = document.querySelector(‘.toys-container’), //контейнер для блоков с игрушками
loadingBlock = document.querySelector(‘.loader’);//контейнер для предзагрузчика
let productsModal = document.getElementById(‘productsModal’),//все модальное окно
modalBody = productsModal.querySelector(‘.modal-body’),//основной текст в модальном окне
modalTitle = productsModal.querySelector(‘.modal-title’),//заголовок модального окна
closeBtns = document.querySelectorAll(‘[data-dismiss=”modal”]’);//кнопки для закрытия модального окна вверху и внизу
let loadImg = new Image();// для загрузки картинки-прелоадера
loadImg.src = ‘images/loader.webp’;
let xhr = new XMLHttpRequest();//для формирования AJAX-запроса
function requestJSON(url, comments=false, id=null ) {
xhr.open(‘GET’, url, true);
loadingBlock.append(loadImg);
xhr.responseType = ‘json’;
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
// console.log(xhr.response);
if (comments) showComments(xhr.response, id);
else showProducts(xhr.response);
}
loadImg.remove();
}
};
xhr.send(null);
xhr.onerror = function () {
alert(`Ошибка соединения`);
};
}
requestJSON(‘js/products.json’);
function showProducts(productData) {
let productsStr = ”;
for (obj of productData) {
let infoDiv = ‘<div class=”info”><div class=”rating”>’;
for (let i = 0; i < 5; i++) {
infoDiv += i < obj.stars ? ‘<span class=”star”></span>’ : ‘<span class=”star-outline”></span>’;
}
const isSale = obj.sale_price ? `<span class=“sale-price”>${obj.sale_price} грн</span>` : ”;
//console.log(obj.name.length);
infoDiv += `</div> <a href=“#” data-id=“${obj.id}” class=“reviews text-right”>${obj.reviews} отзыва</a></div>`;
productsStr += `<div class=“col-lg-3 col-md-6 my-3”>
<div class=“card h-100”>
<div class=“img-holder”>
<img class=“img-fluid” src=“${obj.img}” alt=“${obj.name}”>
</div>
<div class=“card-body”>
${infoDiv}
<div class=“card-title”>${obj.name}</div>
<div class=”prices my-3″><span class=”regular-price${isSale ? ‘ old-price’:”}”>${obj.regular_price} грн</span> ${isSale}</div>
<p class=“card-text d-none”>${obj.descr.replace(/n/g, ‘<br>’)}</p>
<a href=“#productsModal” onclick=“openModal(event)” class=“btn btn-primary” data-id=“${obj.id}”>Подробнее</a>
</div>
</div>
</div>`;
}
toysRow.innerHTML = productsStr;
document.querySelectorAll(‘.reviews’).forEach(link => link.addEventListener(‘click’, showReviews));
}
function openModal(e) {
e.preventDefault();
let link = e.target;
productsModal.style.display = ‘block’;
productsModal.classList.add(‘show’);
document.body.classList.add(‘modal-open’);
modalBody.innerHTML = link.closest(‘.card’).innerHTML;
let imgHolder = modalBody.querySelector(‘.img-holder’)
imgHolder.classList.remove(‘img-holder’);
imgHolder.classList.add(‘text-center’);
modalBody.querySelector(‘.card-text’).classList.remove(‘d-none’);
modalBody.querySelector(‘.btn’).remove();
modalBody.querySelector(‘.reviews’).remove();
modalTitle.innerHTML = “Информация о товаре <strong>”+modalBody.querySelector(‘.card-title’).textContent+“<strong>”;
}
closeBtns.forEach(btn => btn.addEventListener(‘click’, closeModal));
document.querySelector(‘.modal’).addEventListener(‘click’, closeModal);
function closeModal(e) {
if(e.target!== e.currentTarget) return;
productsModal.style.display = ‘none’;
productsModal.classList.remove(‘show’);
document.body.classList.remove(‘modal-open’);
}
function showReviews(evt){
evt.preventDefault();
requestJSON(‘js/reviews.json’, true, this.dataset.id);
}
function showComments(data, id){
//console.log(data, id);
let commentsStr = ”;
data.forEach(item => {
if(item.product_id!=id) return;
let str = item.plus ? ‘<div class=”plus”>’+item.plus+‘</div>’:”;
console.log(str);
let ratingDiv = ‘<div class=”rating”>’;
for (let i = 0; i < 5; i++) {
ratingDiv += i < item.stars ? ‘<span class=”star”></span>’ : ‘<span class=”star-outline”></span>’;
}
ratingDiv+=‘</div>’;
commentsStr +=`<div class=“review”>
<div class=“info”>
<div class=“author”>${item.author}</div>
${ratingDiv}
</div>
<div class=“review-text”>${item.text.replace(/n/g, ‘<br>’)}</div>
${item.plus ? ‘<div class=”plus my-2″><strong>Достоинства: </strong> ‘+item.plus+’</div>’:”}
${item.minus ? ‘<div class=”minus my-2″><strong>Недостатки: </strong> ‘+item.minus+’</div>‘:’‘}
</div>`;
});
productsModal.style.display = ‘block‘;
productsModal.classList.add(‘show‘);
document.body.classList.add(‘modal–open‘);
productsModal.querySelector(‘.modal–title‘).textContent = ‘Отзывы к товару’;
modalBody.innerHTML = commentsStr;
}
|
Скрипт основан на замене текста в одном и том же модальном окне.
С комментариями наш пример выглядит так (открыть в новой вкладке):