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

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

Подписаться

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

Содержание

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

  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

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

Все статьи
Внедрение системы управления персоналом и автоматизация: обзор HRM-систем

Внедрение системы управления персоналом и автоматизация: обзор HRM-систем

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

06.09.2024
7
ELMA365 CRM – система автоматизации и управления бизнес процессами

ELMA365 CRM – система автоматизации и управления бизнес процессами

ELMA365 CRM – это мощная и гибкая CRM-BPM система, разработанная российской компанией ELMA, специально для удовлетворения потре...

02.09.2024
93
Что делать, если Google Документы перестанут работать? Топ-9 альтернатив для России

Что делать, если Google Документы перестанут работать? Топ-9 альтернатив для России

Периодически в интернете появляются слухи о возможной блокировке Google Docs в России. Хотя пока нет серьезных причин для паники, лучше заранее...

27.08.2024
105
13 Альтернатив Miro: лучшие сервисы виртуальных досок для совместной работы

13 Альтернатив Miro: лучшие сервисы виртуальных досок для совместной работы

Miro – это онлайн-платформа для коллективной работы, которая эффективно заменяет традиционные маркерные доски. На Miro пользователи могут созда...

22.08.2024
113
Интеграция Битрикс24 с мессенджерами: преимущества и способы реализации

Интеграция Битрикс24 с мессенджерами: преимущества и способы реализации

В современном бизнесе коммуникация с клиентами становится ключевым фактором успеха. Чтобы эффективно взаимодействовать с клиентами и улучшать п...

21.08.2024
74
Directum RX: Интеллектуальная Платформа для Управления Бизнес-Процессами и Документами

Directum RX: Интеллектуальная Платформа для Управления Бизнес-Процессами и Документами

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

19.08.2024
157
ERP-система для вашего бизнеса: подготовка и успешное внедрение 1С:ERP

ERP-система для вашего бизнеса: подготовка и успешное внедрение 1С:ERP

В современной экономической среде эффективное управление перестало быть просто конкурентным преимуществом, превратившись в необходимое условие ...

15.08.2024
117
Cloudflare CDN: Обзор возможностей, преимуществ и тарифов

Cloudflare CDN: Обзор возможностей, преимуществ и тарифов

В этой статье мы рассмотрим Cloudflare CDN — сеть высокоскоростной доставки динамического контента, основанную на технологии периферийных вычис...

13.08.2024
129