Русский
Русский
English
Статистика
Реклама

Custom elements

Про Shadow DOM

06.09.2020 20:21:10 | Автор: admin

Всем привет!

Продолжаю свой цикл публикаций о группе стандартов Web Components. Моя цель - сформировать реалистичные ожидания от данного набора технологий, а также, вместе с вами, прийти к более четкому пониманию того, где их не стоит применять, и где, напротив, ничего лучше еще не придумано. На этот раз, предлагаю подробнее остановится на Shadow DOM.

Начнем с азов, чтобы те из нас, кто пока не сталкивался с предметом обсуждения, не потеряли интерес к основной части статьи. Итак, Shadow DOM - это часть современного DOM API, которая позволяет создавать изолированные участки документа, со своей собственной внутренней разметкой и своими собственными стилями. Если вы откроете, с помощью инструментов разработчика в браузере, структуру документа, содержащего, к примеру, стандартный HTML-элемент audio - вы увидите в его составе область, обозначенную как #shadow-root и какую-то разметку внутри. Это и есть Shadow DOM элемента audio. Как видите, мы можем столкнуться с "теневыми" участками документов в приложениях и на страницах сайтов, даже если при их создании никак не использовались веб-компоненты или основанные на них библиотеки. Это утверждение справедливо для всех стандартных браузерных UI-примитивов, таких как кнопки, селекты, инпуты и т. д. Хорошая новость в том, что теперь у нас есть возможность создавать собственные универсальные элементы, подобные встроенным браузерным. И Shadow DOM, в данном случае, это ответ на вопрос "как?".

Какие основные вопросы решает Shadow DOM?

  1. Инкапсуляция. Внутри Shadow DOM создается отдельный "поддокумент", к которому можно применять свои стили, экранированные от воздействий внешней среды (вам не нужно писать многоэтажные имена классов, чтобы обезопасить ваш элемент или внешний документ от "протечек") и где создается свой контекст для методов DOM API, где, к примеру, с помощью селекторов можно получить только те элементы которые находятся внутри и остаются почти невидимыми снаружи (при этом, допустимо использование одинаковых ID у элементов в разных контекстах, без опасности все поломать).

  2. Композиция. Shadow DOM дает вам контроль над "размещением" непосредственных потомков сложного DOM-элемента в нужных местах его внутренней разметки. В этом случае, Shadow DOM выступает в роли некоего шаблона, в котором можно размещать содержимое в местах, обозначенных специальным тегом - slot. Это дает возможность, к примеру, создавать элементы-лейауты и повторно использовать их.

У тех, кто уже имел дело с библиотеками, основанными на веб-компонентах (такими как LitElement), могло сформироваться ложное впечатление о том, что Shadow DOM - это неотъемлемая часть любого веб-компонента. Это не так. Вы можете создавать компоненты с помощью стандарта Custom Elements и НЕ использовать Shadow DOM при этом, и напротив, создавать теневой DOM у обычных элементов, таких как старый добрый div. Теневой DOM открывает довольно интересные и нетривиальные возможности, например, когда вы динамически добавляете CSS-свойства к элементу через интерфейс "element.style", у вас нет возможности определить псевдоклассы и псеводоэлементы, а также, использовать media-запросы или создавать ключи анимации. Это большой недостаток модели работы со стилями через JavaScript в современных браузерах (работа в этом направлении ведется, но это отдельная сложная тема). Все меняет Shadow DOM:

let myElement = document.createElement('div');myElement.attachShadow({  mode: 'open',});myElement.shadowRoot.innerHTML = /*html*/ `<style>:host {  padding: 10px;}:host(:hover) {  color: red;}</style><slot></slot>`;

Теперь у нашего div есть реакция на наведение мыши при том, что мы не создавали для этого никаких классов и не вносили никаких изменений во внешние стили. Shadow DOM дает нам доступ к своему элементу-контейнеру через селектор :host, и используя этот селектор, мы можем создавать любые сложные стили для элемента в JS. Прошу принять во внимание, что код приведенный выше, написан исключительно для демонстрации самого принципа, в бою все может выглядеть немного иначе.

Когда стоит применять Shadow DOM?

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

Модные микрофронтенды также являются интересной областью для применения возможностей Shadow DOM.

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

Вялотекущий рефакторинг сложной системы тоже можно проводить через создание "островков безопасности".

Какие могут возникнуть сложности?

Следует понимать, что Shadow DOM в отдельности, НЕ решает вопрос контроля жизненного цикла ваших компонентов и инициализации компонентов во внешней среде (помните, для этого есть Custom Elements).

Shadow DOM в документе может быть создан только через JavaScript, а потому, вы не сможете напрямую использовать предварительный рендер (SSR) для внутренней разметки. Данное ограничение можно обойти, но это отдельный непростой разговор.

В случае, если на сайте используется CSP (Content Security Policy) - вы будете ограничены в выборе способов добавления стилей для элементов внутри теневого DOM. Любая попытка парсить стили из строки вызовет ошибку. Не будет работать ни innerHTML, ни insertRule, ничто иное в этом роде. Самое простое и быстрое решение, но, на мой взгляд, наименее красивое - CSP-флаг unsafe-inline. Если вы создаете виджет для интеграции его на сторонний сайт, рекомендовать пользователям использование небезопасных настроек - это не комильфо. Для браузеров на основе Chromium, выходом может быть использование adoptedStylesheets. Более универсальными решениями будет создание динамических стилей через element.style (что, как писалось выше, имеет свои ограничения), либо добавление в Shadow DOM внешнего файла стилей:

let myElement = document.createElement('div');myElement.attachShadow({  mode: 'open',});myElement.shadowRoot.innerHTML = /*html*/ `<link rel="stylesheet" href="styles.css"><slot></slot>`;

На создание Shadow DOM, при прочих равных условиях, уходят дополнительные ресурсы, поэтому, если для вас важен вопрос производительности, старайтесь не использовать теневые участки DOM бездумно повсюду. Часто для создания виджета достаточно всего одного общего теневого DOM, без лишней вложенности и сопутствующих этому дополнительных расходов.

Ну и, поскольку данный вопрос все еще всплывает в обсуждениях, затрону и его: если среди требований к вашему проекту имеется поддержка IE - теневой DOM не ваш выбор. Радует, что все большее число разработчиков отказывается от поддержки некробраузеров, надеюсь скоро не нужно будет вообще об этом вспоминать.

Вывод

Shadow DOM - мощная и гибкая технология. Ее использование может существенно облегчить решение многих задач и открывает простор для творчества в решении задач нетипичных. Но не ждите от нее волшебного ответа на все свои вопросы и полного отсутствия сложностей.

Подробнее..

Перевод Веб-компоненты руководство для начинающих

21.10.2020 14:12:03 | Автор: admin

Узнайте о преимуществах использования веб-компонентов, о том, как они работают, а также о том, как начать их использовать

С помощью веб-компонентов (далее компоненты) разработчики могут создавать собственные HTML-элементы. В данном руководстве вы узнаете все, что должны знать о компонентах. Мы начнем с того, что такое компоненты, каковы их преимущества, а также из чего они состоят.

После этого мы приступим к созданию компонентов, сначала с помощью шаблонов HTML (HTML templates) и интерфейса теневого DOM (shadow DOM), затем немного углубимся в тему и посмотрим как создать кастомизированный встроенный элемент (customized build-in element).

Что такое компоненты?


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

XML Binding Language от Mozilla и спецификации HTML Component от Microsoft для Internet Explorer 5 появились около 20 лет назад. К сожалению, обе реализации были очень сложными и не смогли заинтересовать производителей других браузеров, а потому вскоре были забыты. Несмотря на это, именно они заложили основы того, что мы имеем в этой сфере сегодня.

JavaScript-фреймворки, такие как React, Vue и Angular используют аналогичный подход. Одной из главных причин их успеха является возможность инкапсуляции общей логики приложения в некие шаблоны, легко переходящие из одной формы в другую.

Хотя данные фреймворки улучшают опыт разработки, за все приходится платить. Особенности языка, такие как JSX, нуждаются в компиляции, и большинство фреймворков используют движок JavaScript для управления своими абстракциями. Существует ли другой подход к решению проблемы разделения кода на компоненты? Ответ веб-компоненты.

4 столпа компонентов


Компоненты состоит из трех интерфейсов (API) кастомные элементы (custom elements), шаблоны HTML (HTML templates) и теневой DOM (shadow DOM), а также из лежащих в их основе модулей JavaScript (ES6 modules). С помощью инструментов, предоставляемых этими интерфейсами, можно создавать кастомные HTML-элементы, которые ведут себя подобно нативным аналогам.

Компоненты используются также, как и обычные элементы HTML. Их можно настраивать с помощью атрибутов, получать с помощью JavaScript, стилизовать с помощью CSS. Главное уведомить браузер о том, что они существуют.

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

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

Рассмотрим каждую спецификацию по-отдельности.



1. Кастомные элементы


Ключевые особенности:

  • Определение поведения элемента
  • Реагирование на изменения атрибутов
  • Расширение существующих элементов

Часто при разговоре о компонентах, люди имеют ввиду интерфейс кастомных элементов.

Этот API позволяет расширять элементы, определяя их поведение при добавлении, обновлении и удалении.

class ExampleElement extends HTMLElement {  static get observedAttributes() {      return [...]  }  attributeChangedCallback(name, oldValue, newValue) {}  connectedCallback() {}}customElements.define('example-element', ExampleElement)

Каждый кастомный элемент имеет похожую структуру. Он расширяет функционал существующего класса HTMLElements.

Внутри кастомного элемента содержится несколько методов, которые называются реакциями (reactions), отвечающих за обработку того или иного изменения элемента. Например, connectedCallback вызывается при добавлении элемента на страницу. Это похоже на стадии жизненного цикла, используемые в фреймворках (componentDidMount в React, mounted в Vue).

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

Элемент должен быть определен перед тем, как браузер сможет его использовать. Метод define принимает два аргумента название тега и его класс. Все теги должны содержать символ "-" во избежание конфликтов с существующими и будущими нативными элементами.

<example-element>Content</example-element>

Элемент может использовать как обычный тег HTML. При обнаружении такого элемента, браузер связывает его поведение с указанным классом. Данный процесс называется обновлением (upgrading).

Существует два типа элементов автономные (autonomous) и кастомизированные встроенные (customized build-in). До сих пор мы рассматривали автономные элементы. Это такие элементы, которые не связаны с существующими HTML-элементами. Подобно тегам div и span, которые не имеют определенного семантического значения.

Кастомизированные встроенные элементы как следует из их названия расширяют функционал существующих элементов HTML. Они наследуют семантическое поведение этих элементов и могут его изменять. Например, если элемент input был кастомизирован, он все равно останется полем для ввода данных и частью формы при ее отправке.

class CustomInput extends HTMLInputElement {}customElements.define('custom-input', CustomInput, { extends: 'input' })

Класс кастомизированного встроенного элемента расширяет класс кастомизируемого элемента. При определении (define) встроенного элемента в качестве третьего аргумента передается расширяемый элемент.

<input is="custom-input" />

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

В то время, как автономные элементы поддерживаются большинством современных браузеров, кастомизированные встроенные элементы поддерживаются только Chrome и Firefox. При использовании последних в браузере, который их не поддерживает, они будут рассматриваться как обычные HTML-элементы, так что, по большему счету, их использование является безопасным даже в таких браузерах.

2. Шаблоны HTML


  • Создание готовых структур
  • Не отображаются на странице до вызова
  • Содержат HTML, CSS и JS

Исторически создание шаблонов на стороне клиента предполагало конкатенацию строк в JavaScript или использование таких библиотек, как Handlebars, разбирающих блоки специальной разметки. Недавно в спецификации появился тег template, который может содержать все, что мы хотим использовать.

<template id="tweet">  <div class="tweet">    <span class="message"></span>      Written by @    <span class="username"></span>  </div></template>

Сам по себе он никак не влияет на страницу, т.е. он не парсится движком, запросы на получение ресурсов (аудио, видео) не отправляются. JavaScript не может получить к нему доступ, а для браузеров это пустой элемент.

const template = document.getElementById('tweet')const node = document.importNode(template.content, true)document.body.append(node)

Сначала мы получаем элемент template. Метод importNode создает копию его содержимого, второй аргумент (true) означает глубокое копирование. Наконец, мы добавляем его на страницу, как любой другой элемент.

Шаблоны могут содержать все, что может содержать обычный HTML, включая CSS и JavaScript. При добавлении элемента на страницу, к нему будут применены стили и запущены скрипты. Помните, что стили и скрипты являются глобальными, а, значит, могут перезаписать другие стили и значения, используемые скриптами.

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

3. Теневой DOM


  • Позволяет избежать конфликта стилей
  • Придумывать названия (классов, например) становится проще
  • Инкапсуляция логики реализации

Объектная модель документа (Document Object Model, DOM) это то, как браузер интерпретирует структуру страницы. Читая разметку, браузер определяет какие элементы какой контент содержат и на основе этого принимает решение о том, что следует отображать на странице. При использовании document.getElemetById(), например, браузер обращается к DOM в поисках нужного элемента.

Для макета (layout) страницы такой подход является приемлемым, но что насчет деталей, скрытых внутри элемента? Например, страницу не должно беспокоить то, какой интерфейс содержится внутри элемента video. Вот где теневой DOM приходит на помощь.

<div id="shadow-root"></div><script>  const host = document.getElementById('shadow-root')  const shadow = host.attachShadow({ mode: 'open' })</script>

Теневой DOM создается в момент применения к элементу. В теневой DOM можно добавлять любой контент, как и в обычный (светлый, light) DOM. На теневой DOM не влияет то, что происходит снаружи, т.е. за его пределами. Обычный DOM также не может получить доступ к теневому напрямую. Это означает, что в теневом DOM мы можем использовать любые названия классов, стили и скрипты и не переживать о возможных конфликтах.

Наилучший результат дает использование теневого DOM вкупе с кастомными элементами. Благодаря теневому DOM при повторном использовании компонента его стили и структура никак не влияют на другие элементы на странице.

ES и HTML модули

  • Добавление при необходимости
  • Предварительная генерация не требуется
  • Все хранится в одном месте

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

Спецификация импортов HTML (HTML Imports) определяет способ экспорта и импорта HTML документов, а также CSS и JavaScript. Это позволило бы кастомным элементам вместе с шаблонами и теневым DOM находится в другом месте и использоваться по необходимости.

Однако, Firefox отказался от реализации данной спецификации в своем браузере и предложил иной способ на основе JavaScript-модулей.

export class ExampleElement external HTMLElement {}import { ExampleElement } from 'ExampleElement.js'

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

Это прекрасно подходит для компонентов. Кастомные элементы, содержащие шаблон и теневой DOM могут экспортироваться из одного файла и использовать в другом.

import { ExampleElement } from 'ExampleElement.html'

Microsoft выдвинула предложение о расширении спецификации JavaScript-модулей экспортом/импортом HTML. Это позволит создавать компоненты с помощью декларативного и семантического HTML. Данная возможность скоро появится в Chrome и Edge.

Создание собственного компонента


Несмотря на множество вещей, связанных с компонентами, которые могут показаться вам сложными, создание и использование простого компонента занимает всего несколько строк кода. Рассмотрим примеров.


Компоненты позволяют отображать комментарии пользователей с помощью интерфейсов шаблонов HTML и теневого DOM.

Создадим компонент для отображения комментариев пользователей с помощью шаблонов HTML и теневой DOM.

1. Создание шаблона

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

Добавляем элемент template на страницу. Любые стили, определенные в этом элементе, будут влиять только на него.

<template id="user-comment-template">  <style>      ...  </style></template>

2. Добавление разметки

Кроме стилей, компонент может содержать макет (структуру). Для этих целей используется элемент div.

Динамический контент передается через слоты (slots). Добавим слоты для аватара, имени и сообщения пользователя с соответствующими атрибутами name:

<div class="container">  <div class="avatar-container">    <slot name="avatar"></slot>  </div>  <div class="comment">    <slot name="username"></slot>    <slot name="comment"></slot>  </div></div>

Содержимое слота по умолчанию


Контент по умолчанию будет отображаться при отсутствии переданной слоту информации

Данные, переданные слоту, перезаписывают данные в шаблоне. Если слоту не передается информации, отображается контент по умолчанию.

В данном случае, если имя пользователя не было передано, на его месте отображается сообщение No name:

<slot name="username">  <span class="unknown">No name</span></slot>

3. Создание класса

Создание кастомного элемента начинается с расширения класса HTMLElement. Частью процесса настройки является создание теневого корневого узла (shadow root) для рендеринга контента элемента. Открываем его для получения доступа на следующем этапе.

Наконец, сообщаем браузеру о новом классе UserComment.

class UserComment extends HTMLElement {  constructor() {      super()      this.attachShadow({ mode: 'open' })  }}customElements.define('user-comment', UserComment)

4. Применение теневого контента

Когда браузер встречает элемент user-comment, он обращается к теневому корневому узлу для получения его содержимого. Второй аргумент указывает браузеру копировать весь контент, а не только первый слой (элементы верхнего уровня).

Добавляем разметку в теневой корневой узел, что приводит к немедленному обновлению внешнего вида компонента.

connectedCallback() {  const template = document.getElementById('user-comment-template')  const node = document.importNode(template.content, true)  this.shadowRoot.append(node)}

5. Использование компонента

Теперь компонент готов к использованию. Добавляем тег user-comment и передаем ему необходимую информацию.

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

<user-comment>  <img alt="" slot="avatar" src="avatar.png" />  <span slot="username">Matt Crouch</span>  <div slot="comment">This is an example of a comment</div></user-comment>

Рсширенный код примера:
<!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <title>Web Components Example</title>    <style>      body {        display: grid;        place-items: center;      }      img {        width: 80px;        border-radius: 4px;      }    </style>  </head>  <body>    <template id="user-comment-template">      <div class="container">        <div class="avatar-container">          <slot name="avatar">            <slot class="unknown"></slot>          </slot>        </div>        <div class="comment">          <slot name="username">No name</slot>          <slot name="comment"></slot>        </div>      </div>      <style>        .container {          width: 320px;          clear: both;          margin-bottom: 1rem;        }        .avatar-container {          float: left;          margin-right: 1rem;        }        .comment {          height: 80px;          display: flex;          flex-direction: column;          justify-content: center;        }        .unknown {          display: block;          width: 80px;          height: 80px;          border-radius: 4px;          background: #ccc;        }      </style>    </template>    <user-comment>      <img alt="" slot="avatar" src="avatar1.jpg" />      <span slot="username">Matt Crouch</span>      <div slot="comment">Fisrt comment</div>    </user-comment>    <user-comment>      <img alt="" slot="avatar" src="avatar2.jpg" />      <!-- no username -->      <div slot="comment">Second comment</div>    </user-comment>    <user-comment>      <!-- no avatar -->      <span slot="username">John Smith</span>      <div slot="comment">Second comment</div>    </user-comment>    <script>      class UserComment extends HTMLElement {        constructor() {          super();          this.attachShadow({ mode: "open" });        }        connectedCallback() {          const template = document.getElementById("user-comment-template");          const node = document.importNode(template.content, true);          this.shadowRoot.append(node);        }      }      customElements.define("user-comment", UserComment);    </script>  </body></html>





Создание кастомизированного встроенного элемента


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

1. Создание класса

Встроенные элементы, как и автономные, появляются в момент расширения класса, но вместо общего класса HTMLElement, они расширяют конкретный класс.

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

class RelativeTime extends HTMLTimeElement {}

2. Определение элемента

Элемент регистрируется браузером с помощью метода define. Однако, в отличие от автономного элемента, при регистрации встроенного элемента методу define необходимо передать третий аргумент объект с настройками.

Наш объект будет содержать один ключ со значением кастомизируемого элемента. Он принимает название тега. В случае отсутствия такого ключа будет выброшено исключение.

customElements.define('relative-time', RelativeTime, { extends: 'time' })

3. Установка времени

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

Наконец, мы устанавливаем атрибут title, позволяющий пользователю при наведении курсора увидеть установленное значение.

setTime() {  this.innerHTML = timeago().format(this.getAttribute('datetime'))  this.setAttribute('title', this.getAttribute('datetime'))}

4. Обновление соединения

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

connectedCAllback() {  this.setTime()}

5. Слежение за изменением атрибутов

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

После определения наблюдаемых атрибутов (observed attributes), attributeChangedCallback будет вызываться при каждом их изменении.

static get observedAttributes() {  return ['datetime']}attributeChangedCallback() {  this.setTime()}

6. Добавление на страницу

Поскольку наш элемент является расширением нативного элемента, его имплементация немного отличается. Для его использования добавляем на страницу тег time со специальным атрибутом is, значением которого является название встроенного элемента, определенного при регистрации. Браузеры, которые не поддерживают компоненты, будут отображать запасной контент.

<time is="relative-time" datetime="2020-09-20T12:00:00+0000">  20 сентября 2020 г. 12:00</time>

Расширенный код примера:
<!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <title>Web Components Another Example</title>    <!-- timeago.js -->    <script      src="http://personeltest.ru/aways/cdnjs.cloudflare.com/ajax/libs/timeago.js/4.0.2/timeago.min.js"      integrity="sha512-SVDh1zH5N9ChofSlNAK43lcNS7lWze6DTVx1JCXH1Tmno+0/1jMpdbR8YDgDUfcUrPp1xyE53G42GFrcM0CMVg=="      crossorigin="anonymous"    ></script>    <style>      body {        display: flex;        flex-direction: column;        align-items: center;      }      input,      button {        margin-bottom: 0.5rem;      }      time {        font-size: 2rem;      }    </style>  </head>  <body>    <input type="text" placeholder="2020-10-20" value="2020-08-19" />    <button>Set Time</button>    <time is="relative-time" datetime="2020-09-19">      19 сентября 2020 г.    </time>    <script>      class RelativeTime extends HTMLTimeElement {        setTime() {          this.innerHTML = timeago.format(this.getAttribute("datetime"));          this.setAttribute("title", this.getAttribute("datetime"));        }        connectedCallback() {          this.setTime();        }        static get observedAttributes() {          return ["datetime"];        }        attributeChangedCallback() {          this.setTime();        }      }      customElements.define("relative-time", RelativeTime, { extends: "time" });      const button = document.querySelector("button");      const input = document.querySelector("input");      const time = document.querySelector("time");      button.onclick = () => {        const { value } = input;        time.setAttribute("datetime", value);      };    </script>  </body></html>





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

Перевод Создание кастомного плагина для October CMS

15.07.2020 08:22:47 | Автор: admin
Мы продолжаем рассматривать October CMS и её особенности. Мы в LOVATA уже 6 лет работаем с этой системой и за это время убедились в том, что её растущая популярность абсолютно заслуженная.

Сегодня мы подготовили перевод еще одной статьи Leonardo Lozoviz. Данная статья посвящена созданию кастомной функциональности через плагины. Предыдущую статью, посвященную сравнению October CMS и WordPress, вы можете найти здесь.


Если вам нравится писать объектно-ориентированный и лёгкий для чтения код, то эта статья для вас. Вы узнаете, как написать свой плагин и почему October может стать хорошим вариантом для вашего следующего проекта.

В прошлом году я провёл небольшое исследование новых CMS, основанных на PHP, в поисках хорошей альтернативы WordPress. В идеале это должно было быть решение с открытым исходным кодом на современной кодовой базе.

Тогда меня заинтересовала October CMS. Я попробовал её и она почти сразу мне понравилась. У October хорошая структура кода, для неё легко писать собственные плагины.

Цель этой статьи помочь вам понять, что собой представляет платформа и чего стоит от неё ожидать, прежде чем вы решите её использовать.

Почему стоит выбрать October в качестве CMS платформы?


Есть несколько основных причин, почему я решил использовать October для своих проектов.

Написана на фреймворке Laravel


October CMS построена на основе Laravel самого мощного PHP-фреймворка для создания современных веб-приложений. Я могу точно сказать, что он лучший. Фреймворк очень прост в использовании, он исключительно понятен. Кроме того, в нём есть все возможности, необходимые современному фреймворку: роутинг (маршрутизация), объектно-реляционное отображение (ORM), авторизация, кэширование и многие другие, обеспечивающие красивую и понятную структуру в соответствии с концепцией Model-View-Controller. Поскольку October CMS написана на фреймворке Laravel, она унаследовала все эти возможности от старшего брата.

Чистый код и документация


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

Вместо старого доброго PHP, October использует Twig в качестве языка для создания шаблонов, что упрощает работу разработчиков. Техническая документация также хорошо написана и позволяет быстро найти ответы на большинство вопросов.

Отличное сообщество


Хотя сообщество October еще не очень большое, люди в нём отзывчивы и всегда готовы помочь. Есть канал в Slack (На данный момент официально заморожен, сообщество переезжает в Discord. Прим. перев.), к которому вы можете присоединиться, и разработчики с радостью помогут решить вашу проблему.

Большой маркетплейс


Как и у WordPress и других CMS, у October есть маркетплейс тем и плагинов. Выбор хороших тем не очень большой, но есть более 700 плагинов (По состоянию на июнь 2020 опубликовано более 900 плагинов и около 200 тем. Прим. перев.), поэтому вполне вероятно, что вы сможете расширить функциональность, просто выполнив поиск и установив один из них. Отличительной особенностью плагинов является то, что их можно легко синхронизировать между всеми вашими проектами, просто добавив свой идентификатор проекта в админке.

Плагины и компоненты


Плагины основа добавления новых функций в October CMS. Плагин может состоять из нескольких файлов и директорий, которые отвечают за регистрацию пользовательских компонентов, моделей, обновление структуры базы данных или добавление переводов. Плагин обычно создаётся в проекте в директории plugins/. Поскольку многие плагины добавляются в маркетплейс для использования другими людьми, каждый плагин должен иметь собственный неймспейс, который обычно начинается с названия компании или разработчика, создавшего плагин. Так, например, если вас зовут Acme и вы создали отличный плагин под названием Blog, ваш плагин будет называться Acme\Blog.

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

image

Как видите, есть файл с именем plugin.php, который отвечает за регистрацию плагина и всех его компонентов в October CMS.

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

image

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

У October CMS большой маркетплейс, где вы можете найти всё, что нужно.

image

В отличие от Wordpress и других популярных CMS, плагины October могут содержать компоненты. Согласно документации October, компоненты являются настраиваемыми модулями, которые можно прикрепить к любой странице (page), паршелу (partial) или лэйауту (layout). Например, форма обратной связи, навигация, FAQ (список часто задаваемых вопросов и ответы на них); по сути, всё, что логично объединить в один модуль, который можно повторно использовать на нескольких страницах.

Компоненты создаются как часть плагина и находятся в субдиректории components/:

image

У каждого компонента есть PHP-файл, например, componentName.php, который определяет компонент, а также необязательную поддиректорию для паршелов (partials). Папка паршелов (partials) для компонента должна иметь то же имя в нижнем регистре, что и сам компонент.

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

namespace Acme\Blog\Components; class BlogPosts extends \Cms\Classes\ComponentBase{    public function componentDetails()    {        return [            'name' => 'Blog Posts',            'description' => 'Displays a collection of blog posts.'        ];    }     // This array becomes available on the page as {{ component.posts }}    public function posts()    {        return ['First Post', 'Second Post', 'Third Post'];    }} 


Как видим, компонент имеет две основные функции. Первая, componentDetails(), предоставляет информацию о компоненте администратору, который будет добавлять и использовать компоненты на своих веб-страницах. Вторая функция, posts(), возвращает пустые посты, которые затем могут быть использованы внутри части компонента (файл blogposts/default.htm), например так:

url = "/blog" [blogPosts]=={% for post in blogPosts.posts %}    {{ post }}{% endfor %}


Чтобы October CMS знала, что наш компонент существует, мы должны зарегистрировать его, используя основной файл плагина внутри функции registerComponents():

public function registerComponents(){    return [        'October\Demo\Components\Todo' => 'demoTodo'    ];}<


Создаём плагин формы обратной связи


Мы напишем плагин для создания формы обратной связи. Вот как он должен работать:

  • Форма будет содержать следующие поля: имя, фамилия, адрес электронной почты, сообщение.
  • Данные будут отправлены на сервер с использованием Ajax.
  • После отправки данных администратор получит электронное письмо с сообщением, которое отправил пользователь. В этой статье мы будем использовать чистую установку October CMS:


image

Начнём создавать наш плагин, запустив команду в терминале, которая генерирует структуру плагина:

php artisan create:plugin progmatiq.contactform 


image

Аргумент progmatiq.contactform содержит имя автора (progmatiq) и имя плагина (contactform).

image

Теперь нужно открыть наш файл plugin.php и изменить информацию о плагине следующим способом:

public function pluginDetails()   {        return [            'name'        => 'Contact Form',            'description' => 'A simple contact form plug-in',            'author'      => 'progmatiq',            'icon'        => 'icon-leaf'        ];    }


Вот несколько других методов, на которые стоит взглянуть:

  • registerComponents()
    Здесь вы можете определить массив компонентов, которые предоставляет ваш плагин.
  • registerPermissions()
    Вы можете зарегистрировать пользовательские разрешения, которые затем сможете использовать в других областях приложения.
  • registerNavigation()
    Вы можете добавить кастомизированный пункт меню с URL-адресом в меню админки.


Давайте создадим компонент ContactForm:

  • Создаём новую папку components/ в корневой директории вашего плагина.
  • Создаём файл contactForm.php в директории components/.


image

  • Вставляем следующий код, который сообщит October, что делает наш компонент. Мы можем сделать это, создав метод componentDetails() внутри нашего компонента.


<?phpnamespace Progmatiq\Contactform\Components;use Cms\Classes\ComponentBase;class ContactForm extends ComponentBase{    public function componentDetails()    {        return [            'name' => 'Contact Form',            'description' => 'A simple contact form'        ];    }}


Теперь нам нужно зарегистрировать компонент внутри плагина. Для этого мы модифицируем метод registerComponents():

public function registerComponents()    {        return [            'Progmatiq\Contactform\Components\ContactForm' => 'contactForm',        ];    }


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

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

  1. В панели администратора перейдите в CMS (1)> Страницы (2) и нажмите + Добавить (3).
  2. Дайте вашей странице имя и URL (4).
  3. Назовите свой файл (5) и выберите макет по умолчанию (6).


image

Добавим наш новый компонент на страницу:

  1. Нажмите Components в левом меню (1), а затем выберите наш компонент Contact form. Как только вы нажмёте на него (2), он должен быть добавлен на страницу.
  2. Нам нужно разместить фрагмент кода, который добавит к нашей странице заголовок, а также визуализировать компонент, используя директиву Twig {% component contactForm %}:


<div class="container">    <h1> Contact </h1>    {% component 'contactForm' %}</dv>


image

Если вы сейчас откроете страницу контактов, то не увидите ничего, кроме заголовка с надписью Контакты.

image

Дело в том, что у нашей формы нет HTML для отображения.

Нам нужно создать файл contactform/default.htm в папке components/.

image

Также нужно добавить следующий HTML-код в файл:

<form method="POST"     data-request="onSend"    data-request-validate    data-request-success="this.reset(); alert('Thank you for submitting your inquiry')">    <div>                <label for="first_name">First Name</label>        <input type="text" name="first_name" class="form-control">        <p data-validate-for="first_name" class="text-danger"></p>     </div>     <div>        <label for="last_name">Last Name</label>        <input type="text" name="last_name" class="form-control">         <p data-validate-for="last_name" class="text-danger"></p>     </div>     <div>        <label for="email">Email</label>        <input type="text" name="email" class="form-control">        <p data-validate-for="email" class="text-danger"></p>     </div>     <div>        <label for="content">Content</label>        <textarea rows="6" cols="20" name="content" class="form-control"></textarea>        <p data-validate-for="content"  class="text-danger"></p>     </div>     <div>        <button type="submit" class="btn btn-primary" data-attach-loading>Send</button>    </div></form>


В основном этот код довольно прост. Однако, в нем есть специальные атрибуты data- *, которые можно использовать в October:

Тег имеет три специальных атрибута:

data-request="onSend"
Этот атрибут сообщает October, что функция onSend из нашего компонента (которую мы собираемся создать дальше) должна вызываться при отправке формы с использованием Ajax.

data-request-validate
включит валидацию формы посредством Ajax с использованием ошибок, которые будут отправлены с сервера, если форма недействительна.

data-request-success="this.reset(); alert('Thank you for submitting your inquiry')"
очищает форму и затем выдает сообщение, если запрос был успешным и не было никаких ошибок проверки или сервера.

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

<p data-validate-for="content"  class="text-danger"></p>


Кнопка отправки имеет атрибут data-attach-loading, который добавляет спиннер и отключает кнопку во время обработки запроса сервером. Это сделано для того, чтобы пользователь не мог снова отправить форму, пока предыдущий запрос не будет обработан.

А вот как теперь выглядит наша страница:

image

Вернёмся к нашему компоненту contactForm.php и создадим вспомогательные методы onSend() и validate(), которые будет отвечать за отправку формы:

public function onSend()    {        // Get request data        $data = \Input::only([            'first_name',            'last_name',            'email',            'content'        ]);         // Validate request        $this->validate($data);         // Send email        $receiver = 'admin@gmail.com';         \Mail::send('progmatiq.contact::contact', $data, function ($message) use ($receiver) {            $message->to($receiver);        });    }     protected function validate(array $data)     {        // Validate request        $rules = [            'first_name' => 'required|min:3|max:255',            'last_name' => 'required|min:3|max:255',            'email' => 'required|email',            'content' => 'required',        ];         $validator = \Validator::make($data, $rules);         if ($validator->fails()) {            throw new ValidationException($validator);        }    }


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

Если валидация прошла успешно, мы отправим электронное письмо нашему администратору.

Примечание: для упрощения я предположил, что почта, на которую мы хотим отправить заявку admin@gmail.com. Убедитесь, что используете собственный адрес электронной почты!

Вот полный код вашего плагина contactForm.php:

<?php namespace Progmatiq\Contactform\Components; use Cms\Classes\ComponentBase;use October\Rain\Exception\ValidationException; class ContactForm extends ComponentBase{    public function componentDetails()    {        return [            'name' => 'Contact Form',            'description' => 'A simple contact form'        ];    }     public function onSend()    {        // Get request data        $data = \Input::only([            'first_name',            'last_name',            'email',            'content'        ]);         // Validate request        $this->validate($data);         // Send email        $receiver = 'admin@gmail.com';         \Mail::send('progmatiq.contact::contact', $data, function ($message) use ($receiver) {            $message->to($receiver);        });    }     protected function validate(array $data)     {        // Validate request        $rules = [            'first_name' => 'required|min:3|max:255',            'last_name' => 'required|min:3|max:255',            'email' => 'required|email',            'content' => 'required',        ];         $validator = \Validator::make($data, $rules);         if ($validator->fails()) {            throw new ValidationException($validator);        }    }}


Как видите, первый аргумент, который принимает функция Mail :: send() имя шаблона адреса электронной почты, которое будет отображаться в теле сообщения. Нам нужно создать его в панели администратора. Перейдите в Настройки> Шаблоны писем и нажмите кнопку Новый шаблон. Затем заполните форму, как показано на экране ниже:

image

Вот тело письма, которое мы собираемся использовать:

You have received a new contact inquiry

**First Name**:
{{ first_name }}
***
**Last Name**:
{{ last_name }}
***
**Email**:
{{ email }}
***
**Message**:
{{ content }}
***


Теперь сохраните шаблон письма. Следующее, что нам нужно сделать, это настроить SMTP-сервер, который будет отправлять электронные письма.
Перейдите в Настройки> Конфигурация почты и заполните все настройки.

image

Конечно, я не поделюсь своей конфигурацией. Используйте собственные настройки.

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

Во-первых, давайте проверим, работает ли валидация, когда мы оставляем поле Content пустым и вводим неверный адрес электронной почты:

image

Валидация работает как положено. Теперь давайте введём правильные данные и посмотрим, будет ли электронное письмо успешно отправлено нашему администратору.

Вот письмо, которое получит admin@gmail.com:

image

После успешной отправки формы пользователь увидит соответствующее сообщение:

image

Вывод


В этой статье мы рассмотрели, что такое плагин и компонент, и выяснили, как их использовать в October CMS.

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

Я надеюсь, что статья была для вас полезна. Если у вас есть вопросы, не стесняйтесь задавать их в комментариях.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru