Цифровой элемент
15 минут на чтение
1905
Отправь статью на почту?

Работа с динамическим контентом

Подписаться

Большинство современных сайтов работают с динамическим контентом, то есть с контентом, который загружается автоматически без перезагрузки страницы с помощью асинхронных (то есть “с задержкой”) запросов, выполняющихся при определенных условиях.

Содержание

Примеры ситуаций динамического контента в коде

  1. Страница товара, пользователь нажимает на кнопку "Купить", появляется модальное окно со множеством информации о текущем статусе корзины. Разметка этого модального окна изначально на странице отсутствует, чтобы страница загружалась быстрее. После нажатия пользователем на кнопку скрипт выполняет асинхронный запрос к серверу, который, в свою очередь, предоставит всю необходимую информацию о статусе корзины для вывода ее в модальном окне.
  2. Страница каталога, изначально отображено 20 карточек товаров. Пользователь пролистывает страницу вниз, подгружаются еще 20 новых элементов. При дальнейшей прокрутке подгрузятся еще +20 товаров и так далее. Если бы на странице каталога сразу отображались сотни карточек товаров, то страница грузилась бы значительно дольше, чем страница с двадцатью элементами.

Пример общей структуры динамического контента в коде:

<body>
<!--	Будет пустым во время загрузки страницы-->
<div id="container-for-rendering"></div>
<!-- В bundle.js происходит асинхронный запрос, запрашивающий информацию при определенном действии пользователя -->
<script src="bundle.js"></script>
</body> 

Особенности работы с динамическим контентом на примере классовых компонентов

При загрузке страницы скрипт ищет все соответствующие селектору DOM-элементы на странице и привязывает к ним определенный функционал:

<body>
	<div id="static">
		<label>
			<input type="text" placeholder="+7 (999) 999-99-99" data-js-mask-input>
		</label>
	
		<label>
			<input type="text" placeholder="+7 (999) 999-99-99" data-js-mask-input>
		</label>
	
		<label>
			<input type="text" placeholder="+7 (999) 999-99-99" data-js-mask-input>
		</label>
	</div>
	
	<script src="bundle.js"></script>
</body> 
 import IMask from 'imask'

export default class Masks {
  els = {
    instance: '[data-js-mask-input]'
  }
  
  constructor() {
    this.init()
  }
  
  init() {
    document.querySelectorAll(this.els.instance).forEach((el) => {
      new IMask(el)
    })
  }
}

Если изначально на странице отсутствуют DOM-элементы, к которым происходит обращение в скрипте, то после их динамического появления на странице необходимо запустить скрипт повторно для того, чтобы функционал компонента заработал:

 <!-- Будет пустым во время загрузки страницы -->
<div id="container-for-rendering">
 <!-- Подгрузится асинхронно --> 
<label> 
<input type="text" placeholder="+7 (999) 999-99-99" data-js-mask-input> 
</label> 
</div>

Общий алгоритм работы с динамическим контентом:

  1. Запросить динамическое содержимое посредством ajax/fetch;
  2. Вставить загруженный контент в необходимое место;
  3. Вызвать CustomEvent - всплывающее событие, сигнализирующее о том, что на странице появился новый контент и содержащее в себе его HTML nodes.
  4. Подхватить событие и вызвать реинициализацию метода класса.

Запрос и вставка динамического содержимого

export default class MyClass {
  els = {
    containerForRendering: '#container-for-rendering'
  }

  // ...

  render(markup) {
    document.querySelector(this.els.containerForRendering).insertAdjacentHTML('afterend', markup)
  }

  async getMarkup(url, cfg) {
    const resp = await fetch(url, cfg)
    return (resp.ok) ?
      await (cfg.type === 'json') ?
        resp.json() : resp.text() :
        Promise.reject(resp.statusText)
  }
  
  updateMarkup() {
    this.getMarkup.then((markup) => {
      this.render(markup)
    })
  }
}

Асинхронный запрос вернёт разметку, которая затем добавится в конец элемента для динамического контента.

Работа с CustomEvents

const el = document.querySelector('#my-element')
const event = new Event('myCustomEventName')

// Подписываемся на событие
el.addEventListener('myCustomEventName', (e) => {
  // ...
})

// Вызываем событие
el.dispatchEvent(event)

Помимо стандартных событий в JavaScript можно создавать свои события, подписывать на них элементы и вызывать их при необходимости.

Кастомные события с дополнительными данными

const el = document.querySelector('#my-element')

// Подписываемся на событие
el.addEventListener('myCustomEventName', (e) => {
  // ...
})

// Создаем событие с данными
const customEvent = new CustomEvent('myCustomEventName', {
  detail: {} // любые данные
})

// Вызываем событие
el.dispatchEvent(customEvent)

Любое кастомное событие можно дополнить абсолютно любыми данными, которые могут пригодиться после дальнейшего “отлова” события.

Функция для уведомления о появлении нового контента

// ./js/generic/evented.js

export const dispatchContentLoaded = (data) => {
  document.dispatchEvent(
    new CustomEvent('contentLoaded', {
      detail: data
    })
  )
}

Работа с событиями появления нового контента

import {dispatchContentLoaded} from './generic/eventing'

export default class MyClass {
  els = {
    containerForRendering: '#container-for-rendering'
  }

  // ...

  render(string) {
    const dynamicNode = document.querySelector(this.els.containerForRendering)
    
    dynamicNode.insertAdjacentHTML('afterend', string)
    dispatchContentLoaded({
      content: dynamicNode
    })
  }
}

Функция dispatchContentLoaded инкапсулирует (скрывает “под капот”) логику вызова кастомного события, что позволяет легко и просто уведомить другие скрипты о появлении динамического контента на странице, вызвав данную функцию после успешного выполнения асинхронного запроса и отрисовки новой разметки.

Функция подписки на обновление (на примере классового компонента)

export class AccordionCollection {
  // Коллекция экземпляров
  _collection = []

  constructor() {
    super(instance, Accordion)
    this.init()
    this.bindEvents()
  }

  // Ищет внутри коллекции по DOM-элементу. У экземпляров класса должен быть параметр instance, по нему идет проверка
  getByDOMElement(DOMElement) {
    return this.collection.find(item => item.instance === DOMElement)
  }

  // Добавляет экземпляр в коллекцию. По-умолчанию проверяет, существует ли экземпляр с таким instance.
  set collection(newCollectionItem) {
    const itemInCollection = this.getByDOMElement(newCollectionItem.instance)
    if (!itemInCollection) {
      this._collection = [...this._collection, newCollectionItem]
    }
  }

  // Публичная коллекция
  get collection() {
    return this._collection
  }

  init(context = document) {
    context.querySelectorAll(instance).forEach((el) => {
      this.collection = new Accordion(el)
    })
  }

  bindEvents() {
    onContentLoaded((e) => { // слушаем обновление контента
      this.init(e.detail.content) // e.detail.content - обновленное содержимое (html nodes)
    })
  }
}

В конструкторе класса, помимо инициализации (вызова метода init), происходит привязка события отслеживания появления динамического контента. И при появлении новых элементов произойдет повторная инициализация компонента с контекстом e.detail.content (поиск новых DOM-элементов произойдет в контексте конкретного элемента, полученного в e.detail.content).

Работа с экземплярами класса Accordion происходит через так называемую коллекцию. Коллекцией в данном контексте является совокупность компонентов, неоднократно повторяющихся на странице. В рамках коллекции можно реализовать различные вспомогательные методы для более гибкого управления экземплярами класса, например, метод получения доступа к конкретным инстансам экземпляра коллекции.

Обработка событий при статическом контенте

export default class MyClass {
  els = {
    something: '[data-js-my-clickable-element]' 
  }

  constructor() {
    this.bindEvents()
  }
  
  handleClick(e) {
    // ...
  }
  
  bindEvents() {
    document.querySelectorAll(this.els.something).forEach((node) => {
      node.addEventListener('click', (e) => this.handleClick(e))
    })    
  }
}

Обработка событий при динамическом контенте

export default class MyClass {
  els = {
    something: '[data-js-my-clickable-element]'
  }

  constructor() {
    this.bindEvents()
  }

  handleClick(e) {
    if (e.target.matches(this.els.something) || e.target.closest(this.els.something)) {
      // обработка клика
    }
  }

  bindEvents() {
    document.addEventListener('click', (e) => this.handleClick(e))
  }
}

Подход к обработке событий статического и динамического контента немного отличается.

Для статического достаточно привязать событие к уже имеющимся на момент загрузки страницы DOM-элементам.

Для динамического контента такой подход не сработает, так как к новым элементам привязки событий уже не будет. Поэтому необходимо добавить обработчик события на элемент document (корневой элемент, то есть буквально на всю страницу), затем “отловить” цель события через e.target.matches (или через e.target.closest, что, правда, чуть менее эффективно, но в некоторых случаях необходимо) и далее обработать событие.

Заключение

Работа с динамическим контентом меняет парадигму мышления разработчика, ведь нельзя рассчитывать на то, что при запуске страницы будут все необходимые элементы для работы конкретного функционала компонента, ибо даже небольшая задержка до появления контента после асинхронного запроса “поломает” неподготовленный к подобному поведению компонент. В лучшем случае - не заработает конкретный функционал, привязанный к элементу. В худшем - “положит” намертво всю страницу целиком.

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

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


Мне не нравится
Россия, Челябинская область, Челябинск, ул. Энтузиастов, 2, оф. 200 Телефон: +7 (351) 220-45-35

Читайте в нашем блоге

Все статьи
Как восстановить доступ в панель администрирования сайта на 1С-Битрикс?

Как восстановить доступ в панель администрирования сайта на 1С-Битрикс?

Через панель администратора сайта на 1С-Битрикс можно управлять настройками сайта, менять контент и так далее. Также там можно заводить новых п...

21.12.2023
413
Как создать аккаунт разработчика в App Store, Google Play, AppGallery

Как создать аккаунт разработчика в App Store, Google Play, AppGallery

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

08.12.2023
1053
Файлы-куки: как правильно информировать пользователей и избежать штрафов

Файлы-куки: как правильно информировать пользователей и избежать штрафов

Веб-аналитика и маркетинг сегодня немыслимы без использования куки-файлов (cookies) - небольших фрагменты данных, которые веб-сайты сохраняют в...

06.12.2023
266
Безопасность сайта: поиск вирусов и троянов

Безопасность сайта: поиск вирусов и троянов

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

20.11.2023
639
«Цифровой Элемент» принял участие в Русском Экономическом Форуме

«Цифровой Элемент» принял участие в Русском Экономическом Форуме

Форум посвящен масштабным вопросам развития суверенной экономики России в XXI веке. Среди основных тем: импортозамещение, технологическое разви...

13.11.2023
294
Новый закон о запрете регистрации на российских сайтах с помощью иностранных электронных почтовых сервисов

Новый закон о запрете регистрации на российских сайтах с помощью иностранных электронных почтовых сервисов

Давайте разберемся, что это значит для владельцев сайтов и пользователей.

Что именно предписывает закон

С 1 декабря ...

09.11.2023
772
Провели курсы «Управленческие вызовы цифровой среды: инструменты для эффективного управления проектами» для госсектора

Провели курсы «Управленческие вызовы цифровой среды: инструменты для эффективного управления проектами» для госсектора

В октябре наша компания провела ряд онлайн-интенсивов «Современные навыки цифрового лидера» для сектора государственного управления Росс...

26.10.2023
502
Перевод сайта на 1C-Битрикс на PHP 8.x: пошаговая инструкция

Перевод сайта на 1C-Битрикс на PHP 8.x: пошаговая инструкция

Вначале года компания 1С-Битрикс выпустила обновления для всех модулей и штатных компонентов, чтобы они могли работать с PHP 8. Пару месяцев на...

16.10.2023
2732