Используем классы в JS на реальном примере (выводим карточки товара на странице сайта)

Обновлено 29.08.2020

Подготовка к работе


Для начала создайте папку с нашим проектом, назовём её practicum, внутри неё создайте папки css, img и js.

В папке css создайте файл style.css, внутри него пропишите следующие стили оформления для наших карточек товара:

Код

@import url(https://fonts.googleapis.com/css?family=Roboto:300,400,700&display=swap&subset=cyrillic-ext);
*{
  box-sizing:border-box;
  margin:0;
  padding:0;
  font-family:Roboto,sans-serif
}
a{
  text-decoration:none
}

.menu{
  width: 1100px;
  padding:40px;
  margin: 0 auto;
}
.menu .container{
  display:flex;
  justify-content:space-between;
  align-items:flex-start
}

.menu__field{
  margin-top:50px;
  padding:50px 0;
  width:100%;
}
.menu__item{
  width:320px;
  min-height:450px;
  background:#fff;
  box-shadow:0 4px 25px rgba(0,0,0,.25);
  font-size:16px;
  font-weight:300;
  padding: 20px;
}
.menu__item img{
  max-width:100%;
  height:auto;
  object-fit:cover;
}
.menu__item-subtitle{
  font-weight:700;
  font-size:18px;
  padding:0 20px;
  margin-top:20px
}
.menu__item-descr{
  margin-top:20px;
  padding:0 20px
}
.menu__item-divider{
  width:100%;
  height:1px;
  background-color:rgba(0,0,0,.25);
  margin-top:40px
}
.menu__item-price{
  display:flex;
  justify-content:space-between;
  align-items:center;
  padding:10px 20px
}
.menu__item-price span{
  font-size:24px;
  font-weight:700
}

В папке js создайте файл script.js и пока оставьте его пустым. Ну и в корневой директории папки с проектом создайте, наконец, файл index.html, его код следующий:

Код

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Используем классы на практике</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>

  <div class="menu">
  <div class="menu__field">
  <div class="container">

  </div>
  </div>
  </div>

   
  <script src="js/script.js"></script>
</body>
</html>

В папке img разместите три файла изображений, они нам понадобятся для карточек товаров (файлы прикреплены в конце урока)
У вас должна получиться такая структура проекта:



Работаем с файлом JS-скрипта


Суть нашего будущего скрипта состоит в том, чтобы написать родительский шаблон и на его основе выводить карточки товары, изменяя лишь передаваемые аргументы, как то, картинку, заголовок, описание и прочее.

В качестве шаблона мы будем использовать класс, назовём его MenuCard:

Код

window.addEventListener('DOMContentLoaded', function() {
  class MenuCard {
   
  }
});

По аналогии с примером, разобранным нами в статье про классы в JS, прописываем внутри него метод constructor():

Код

window.addEventListener('DOMContentLoaded', function() {
  class MenuCard {
  constructor() {
   
  }
  }
});

Теперь нам нужно определиться, что именно мы будем передавать в качестве аргументов для constructor.
Логично предположить, что передавать нам нужно будет то, что будет в каждой карточке товара отличаться.

Посмотрим на карточки товара, которые у нас в итоге должны выводиться на страницу сайта:



Если сравнить все три карточки, то мы видим, что в целом они выглядят одинаково, то есть, имеют общую структуру, но при этом у них разные изображения(и конечно же, будут разные описания для изображений в теге alt), заголовки, описания и значения в графе цена.
Примем все эти различия в качестве передаваемых в конструктор аргументов:

Код

window.addEventListener('DOMContentLoaded', function() {
  class MenuCard {
  constructor(src, alt, title, descr, price) {

  }
  }
});

src - путь к изображению
alt - альтернативный текст для изображения
title - заголовок в карточке товара
descr - описание, следующее после заголовка
price - значение цены товара в рублях

Записываем эти все аргументы в свойства конструктора:

Код

window.addEventListener('DOMContentLoaded', function() {
  class MenuCard {
  constructor(src, alt, title, descr, price) {
  this.src = src;
  this.alt = alt;
  this.title = title;
  this.descr = descr;
  this.price = price;
  }
  }
});

Чтобы более наглядно изучить работу с классами, давайте добавим, помимо свойств, какой-нибудь метод внутрь конструктора, который нам, к примеру, будет переводить стоимость товара из долларов в рубли.

Добавляем метод, назовём его changeToRUS():

Код

window.addEventListener('DOMContentLoaded', function() {
  class MenuCard {
  constructor(src, alt, title, descr, price) {
  this.src = src;
  this.alt = alt;
  this.title = title;
  this.descr = descr;
  this.price = price;
  }

  changeToRUS() {
  this.price = this.price * this.transfer;  
  }
  }
});

Для работы этого метода добавим ещё одно свойство this.transfer внутрь конструктора с каким-то постоянным значением курса доллара, например, 74.

Код

window.addEventListener('DOMContentLoaded', function() {
  class MenuCard {
  constructor(src, alt, title, descr, price) {
  this.src = src;
  this.alt = alt;
  this.title = title;
  this.descr = descr;
  this.price = price;
  this.transfer = 74;
  }

   
  changeToRUS() {
  this.price = this.price * this.transfer;  
  }
  }
});

Таким образом, внутри нашего метода путём перемножения значения (в долларах), которое придёт в аргумент price и постоянного значения свойства transfer, будет происходить конвертация в рубли, результат которой будет записан опять же в свойство price.

Добавляем в конструктор вызов нашего метода конвертации:

Код

window.addEventListener('DOMContentLoaded', function() {
  class MenuCard {
  constructor(src, alt, title, descr, price) {
  this.src = src;
  this.alt = alt;
  this.title = title;
  this.descr = descr;
  this.price = price;
  this.transfer = 74;
  this.changeToRUS();
  }

   
  changeToRUS() {
  this.price = this.price * this.transfer;  
  }
  }
});

Теперь нам также в конструкторе нужно прописать метод render(), который будет отвечать за вывод вёрстки карточек товара на страницу сайта:

Код

window.addEventListener('DOMContentLoaded', function() {
  class MenuCard {
  constructor(src, alt, title, descr, price) {
  this.src = src;
  this.alt = alt;
  this.title = title;
  this.descr = descr;
  this.price = price;
  this.transfer = 74;
  this.changeToRUS();
  }

   
  changeToRUS() {
  this.price = this.price * this.transfer;  
  }

  render() {
   
  }
  }
});

Что будет данный метод делать первым делом? Сначала необходимо создать какой-то элемент, чтобы помещать в него впоследствии карточку товара, допустим, это будет какой-то блок div, его мы, в свою очередь, сохраним в переменную с названием element:

Код

  render() {
  const element = document.createElement('div');
  }

Теперь мы можем с помощью innerHTML сформировать структуру этого блока:

Код

  render() {
  const element = document.createElement('div');
  element.innerHTML = `
  <div class="menu__item">
  <img src="Путь к изображению" alt="Альтернативный текст">
  <h3 class="menu__item-subtitle">Заголовок</h3>
  <div class="menu__item-descr">Описание</div>
  <div class="menu__item-divider"></div>
  <div class="menu__item-price">
  <div class="menu__item-cost">Цена:</div>
  <div class="menu__item-total"><span>Значение</span> руб</div>
  </div>
  </div>
  `;
  }

Получается, что при вызове метода render() каждый блок с классом menu__item будет помещаться в отдельный блок div на странице.

Как поместить эту структуру на страницу сайта? Для этого нам нужно найти родительский элемент и в методе render() конкретно указать, куда будут размещаться карточки товара.

Для этого создадим еще один аргумент для конструктора, назовём его parentSelector:

Код

  class MenuCard {
  constructor(src, alt, title, descr, price, parentSelector) {
  this.src = src;
  this.alt = alt;
  this.title = title;
  this.descr = descr;
  this.price = price;
  this.transfer = 74;
  this.changeToRUS();
  }

В этот аргумент мы будем передавать нужный селектор того элемента, куда мы будем помещать карточки.
Также добавляем его в конструктор как свойство:

Код

  class MenuCard {
  constructor(src, alt, title, descr, price, parentSelector) {
  this.src = src;
  this.alt = alt;
  this.title = title;
  this.descr = descr;
  this.price = price;
  this.parent = document.querySelector(parentSelector);
  this.transfer = 74;
  this.changeToRUS();
  }

И теперь, когда у нас в свойстве parent есть DOM-элемент, мы можем его использовать, вспоминая, что есть метод append(), который позволяет вставить элемент в конец другого элемента:

Код

  render() {
  const element = document.createElement('div');
  element.innerHTML = `
  <div class="menu__item">
  <img src="Путь к изображению" alt="Альтернативный текст">
  <h3 class="menu__item-subtitle">Заголовок</h3>
  <div class="menu__item-descr">Описание</div>
  <div class="menu__item-divider"></div>
  <div class="menu__item-price">
  <div class="menu__item-cost">Цена:</div>
  <div class="menu__item-total"><span>Значение</span> руб</div>
  </div>
  </div>
  `;

  this.parent.append(element);
  }

Теперь нам нужно настроить тот шаблон карточки, который их будет выводить на страницу, ведь у каждой карточки товара будет своя картинка, заголовок и так далее, поэтому заменяем изменяемые данные в шаблоне на значения свойств конструктора:

Код

  render() {
  const element = document.createElement('div');
  element.innerHTML = `
  <div class="menu__item">
  <img src=${this.src} alt=${this.alt}>
  <h3 class="menu__item-subtitle">${this.title}</h3>
  <div class="menu__item-descr">${this.descr}</div>
  <div class="menu__item-divider"></div>
  <div class="menu__item-price">
  <div class="menu__item-cost">Цена:</div>
  <div class="menu__item-total"><span>${this.price}</span> руб</div>
  </div>
  </div>
  `;
  this.parent.append(element);
  }

Теперь наш класс готов к использованию. Чтобы его использовать, создаём новый объект и вызываем метод render(), можно сделать это таким образом:

Код

const div = new MenuCard();
div.render();

Но можно записать и короче, без предварительного сохранения объекта в переменную (например, если на этот объект нам потом нет надобности ссылаться):

Код

new MenuCard().render();

Пропишем внутри аргументы, которые нам нужно передать для нашего класса, который мы создали ранее:

Код

  new MenuCard(
  "img/camry.png",
  "camry",
  'Toyota Camry',
  'Toyota Camry - эталон комфорта и безопасности.',
  25300,
  ".menu .container"
  ).render();

  new MenuCard(
  "img/lendcruser.png",
  "lendcruser",
  'Toyota Land Cruiser 200',
  'Легендарный внедорожник Toyota Land Cruiser 200.',
  80000,
  ".menu .container"
  ).render();

  new MenuCard(
  "img/rav4.png",
  "rav4",
  'Toyota Rav 4',
  'Тойота РАВ 4- это пятиместный компактный кроссовер.',
  22800,
  ".menu .container"
  ).render();

Как вы заметили, в качестве родительского селектора мы передаём класс .container из блока с классом .menu
Блок с классом .container - это будет единый блок-обёртка, внутри которого будут все наши карточки товаров.

Так, с помощью одного созданного нами класса мы настроили шаблон и вывели три различные карточки товара.

Используем Rest оператор для кастомизации скрипта


Но есть одно но. Структура наших карточек такова, что каждая из них, уже имея блок-обёртку с классом menu__item, дополнительно оборачивается в блок div.
Наша задача: удалить в вёрстке блок div с класом menu__item, а вместо этого класс menu__item назначить динамически второму блоку-обёртке div.



Это сделает, во-первых, наш код более правильным, а во-вторых, даст возможность динамически добавлять не только класс menu__item, но и какие-то другие, если в этом возникнет необходимость.

Для реализации такого функционала мы будем использовать Rest-оператор. И для начала мы его прописываем в параметрах конструктора и в его свойствах, дадим ему название classes.

Код

  class MenuCard {
  constructor(src, alt, title, descr, price, parentSelector, ...classes) {
  this.src = src;
  this.alt = alt;
  this.title = title;
  this.descr = descr;
  this.price = price;
  this.classes = classes;
  this.parent = document.querySelector(parentSelector);
  this.transfer = 74;
  this.changeToRUS();  
  }

Не забываем, что rest оператор возвращает массив, поэтому нам нужно его обработать, чтобы из него суметь достать те классы, которые в нём будут впоследствии и привязать их динамически к блоку-обёртке div.
Обработку массива, как мы знаем, лучше всего сделать с помощью forEach(), поэтому переходим в метод render и в нём прописываем перебор массива, внутри метода forEach используем стрелочную функцию с одним аргументом, назовём его className. Эта функция будет добавлять к блоку-обёртке div все классы из массива, найденные во время его перебора:

Код

  render() {
  const element = document.createElement('div');
  this.classes.forEach(className => element.classList.add(className));
  element.innerHTML = `
  <div class="menu__item">
  <img src=${this.src} alt=${this.alt}>
  <h3 class="menu__item-subtitle">${this.title}</h3>
  <div class="menu__item-descr">${this.descr}</div>
  <div class="menu__item-divider"></div>
  <div class="menu__item-price">
  <div class="menu__item-cost">Цена:</div>
  <div class="menu__item-total"><span>${this.price}</span> руб</div>
  </div>
  </div>
  `;
  this.parent.append(element);
  }

Теперь можно убрать блок-обёртку с классом menu__item:

Код

  render() {
  const element = document.createElement('div');
  this.classes.forEach(className => element.classList.add(className));
  element.innerHTML = `
  <img src=${this.src} alt=${this.alt}>
  <h3 class="menu__item-subtitle">${this.title}</h3>
  <div class="menu__item-descr">${this.descr}</div>
  <div class="menu__item-divider"></div>
  <div class="menu__item-price">
  <div class="menu__item-cost">Цена:</div>
  <div class="menu__item-total"><span>${this.price}</span> руб</div>
  </div>
  `;
  this.parent.append(element);
  }

Теперь нам остаётся лишь прописать нужный нам класс menu__item в аргументах наших объектов класса, для первого объекта я добавил также еще класс test, чтобы убедиться, что динамически можно добавить также любой другой класс при необходимости:

Код

  new MenuCard(
  "img/camry.png",
  "camry",
  'Toyota Camry',
  'Toyota Camry - эталон комфорта и безопасности.',
  25300,
  ".menu .container",
  'menu__item',
  'test'
  ).render();

  new MenuCard(
  "img/lendcruser.png",
  "lendcruser",
  'Toyota Land Cruiser 200',
  'Легендарный внедорожник Toyota Land Cruiser 200.',
  80000,
  ".menu .container",
  'menu__item'
  ).render();

  new MenuCard(
  "img/rav4.png",
  "rav4",
  'Toyota Rav 4',
  'Тойота РАВ 4- это пятиместный компактный кроссовер.',
  22800,
  ".menu .container",
  'menu__item'
  ).render();



Нужно также предусмотреть возможность того, что в объекте класса new MenuCard не будет указано ни одного класса в качестве параметра, то есть предусмотреть применение класса menu__item как класса по умолчанию.

Для этого мы в методе render можем использовать обычную управляющую инструкцию, проверяющую условие, что если в this.classes ничего не передаётся, то нужно использовать класс menu__item по умолчанию, а так как this.classes это массив, то используем свойство length массива, чтобы узнать, есть в нём элементы или нет( то есть больше ли длина массива нуля или равна нулю):

Код

  render() {
  const element = document.createElement('div');
  if (this.classes.length === 0) {
  this.classes = 'menu__item';
  element.classList.add(this.classes);
  } else {
  this.classes.forEach(className => element.classList.add(className));
  }

  element.innerHTML = `
  <img src=${this.src} alt=${this.alt}>
  <h3 class="menu__item-subtitle">${this.title}</h3>
  <div class="menu__item-descr">${this.descr}</div>
  <div class="menu__item-divider"></div>
  <div class="menu__item-price">
  <div class="menu__item-cost">Цена:</div>
  <div class="menu__item-total"><span>${this.price}</span> руб</div>
  </div>
  `;
  this.parent.append(element);
  }

Если число элементов в массиве равно нулю(массив пустой), то мы добавляем в него класс по умолчанию, а именно menu__item, и затем этот класс добавляется к каждому элементу с блоком-обёрткой div.

Оцените результат работы скрипта здесь.

Комментарии к материалу: