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; } |
Скрипт основан на замене текста в одном и том же модальном окне.
С комментариями наш пример выглядит так (открыть в новой вкладке):