Обучение старых трюков HTML новым трюкам является основным направлением современных фреймворков JavaScript. Будь то, следуя потенциальным стандартам, таким как WebComponents , создавая пользовательские директивы или расширяя существующие классы , велика вероятность того, что выбранная вами структура предоставляет средство для расширения самой разметки HTML. В предыдущей статье , написанной Брэдом Барроу, вы познакомились с новым игроком: Аурелией . Эта статья будет основана на статье и коде Брэда и покажет вам, как создавать собственные элементы, а также настраиваемые атрибуты, следуя соглашениям Аурелии.
Полный код этой статьи можно найти в нашем репозитории GitHub, и вы можете увидеть демонстрацию того, что мы собираемся построить здесь (пожалуйста, подождите некоторое время для инициализации приложения).
Зачем нужна дополнительная разметка?
Прежде чем перейти непосредственно к действию, давайте сначала разберемся с потенциальным вариантом использования для создания новых компонентов. Для этого мы взглянем концептуально на вводный пример, показанный на рисунке ниже. У нас есть две страницы, представленные ViewModel
(VM) и View
, показывающие забавные картинки и gif-видео. Каждый из них имеет повторяющийся список, который сам отображает сообщения, содержащие изображение и текстовый блок.
Aurelia Reddit Клиент концептуальная схема
Посмотрев на View, мы увидим, что сбор данных, а также рендеринг тесно связаны в одну пару VM / View.
<template> <ul class="list-group"> <li class="list-group-item" repeat.for="p of posts"> <img src.bind="p.data.thumbnail" /> <a href="http://reddit.com${p.data.permalink}"> ${p.data.title} </a> </li> </ul> </template>
Это может не быть проблемой для простого примера, но может превратиться в серьезный недостаток по мере роста системы и сбора все новых и новых требований.
Улучшение существующих элементов с помощью пользовательских атрибутов
Представьте, что мы получаем запрос на предоставление всплывающего окна для каждой из забавных страниц. Чтобы сделать это, мы могли бы легко подключить функцию Bootstrap непосредственно к разметке, поместив необходимые атрибуты data-
последующей инициализацией внутри нашего FunnyVM
. Но что, если нам вдруг понадобится сделать это и на другой странице? Предоставление функции путем объявления пользовательского атрибута может значительно облегчить нашу жизнь. Это особенно полезно в следующих сценариях:
- Упаковка существующих плагинов
- Ярлыки для общих привязок, таких как стиль или класс
- Изменение существующих элементов HTML / пользовательских элементов без прямого доступа к коду
Теперь давайте запачкаем руки и посмотрим, что нужно для создания нашего первого пользовательского атрибута.
Создание поповера
Давайте начнем с того, что мы хотели бы достичь. Новый атрибут popover
должен принимать параметры для placement
, title
и content
всплывающего окна. Размещение фиксируется справа, поэтому достаточно простой строки в качестве значения. Для двух других свойств мы будем использовать привязку данных Aurelia для сопоставления повторяющихся значений. Для загрузки файла мы используем функцию require
Aurelia. Атрибут from
содержит относительный путь к импортируемому ресурсу.
<require from="./popover"></require> ... <img src.bind="p.data.thumbnail" popover="placement: 'right'; title.bind: p.data.url; content.bind: p.data.title" />
Чтобы это произошло, мы начнем с создания нового файла JavaScript в папке src
именем popover.js
. Пользовательский атрибут, как и все другие конструкции Aurelia, представляет собой простой экспортируемый класс ES6, а не набор функций, передаваемых в предопределенный API (как это делают многие устаревшие платформы).
import {customAttribute, bindable, inject} from 'aurelia-framework'; import $ from 'bootstrap'; import bootstrap from 'bootstrap'; ...
По сравнению с другими платформами Aurelia объявляет конструкции, описывая их с помощью metadata
. Но вместо того, чтобы использовать статические функции или сложные API, Aurelia использует передовые ES7 Decorators для достижения этой цели. Мы собираемся импортировать необходимые декораторы из пакета aurelia-framework
. Что касается самого элемента управления, мы будем использовать JavaScript-элемент управления Popover, предоставляемый Twitter Bootstrap. Поэтому мы импортируем дескриптор jQuery $
а также bootstrap
, чтобы инициализировать код JavaScript Bootstraps.
Следующим шагом является применение ранее упомянутых метаданных, чтобы Aurelia знала, что получает, когда загружает файл. Прикрепляя декоратор customAttribute
мы customAttribute
наш компонент заданным значением. bindable
декоратор, с другой стороны, объявляет свойство, к которому может привязываться наш View. Мы просто повторяем этот декоратор для каждого доступного свойства.
@inject(Element) @customAttribute('popover') @bindable('title') @bindable('content') @bindable('placement') export class Popover { ...
Первый inject
Decorator заботится о предоставлении фактического элемента DOM в качестве параметра для нашего метода конструктора, который затем сохраняется для последующего использования.
constructor(element) { this.element = element; }
Теперь, когда у нас есть вся необходимая информация, мы можем включить жизненный цикл Поведений , объявив метод bind
. Это гарантирует, что мы инициализируем компонент в надлежащее время, сравнимое с готовым методом jQuery .
bind() { // initialize the popover $(this.element).popover({ title: this.title, placement: this.placement, content: this.content, trigger: 'hover' }); }
И последнее, но не менее важное: мы добавили измененные обработчики. Обратите внимание, что они на самом деле не выполняются в нашем примере, так как источник привязки не меняется с течением времени.
titleChanged(newValue){ $(this.element).data('bs.popover').options.title = newValue; } contentChanged(newValue){ $(this.element).data('bs.popover').options.content = newValue; } placementChanged(newValue){ $(this.element).data('bs.popover').options.placement = newValue; }
Посмотреть полный файл на GitHub
Теперь, когда мы увидели, как вы можете добавлять новые функции, предоставляя атрибуты существующим элементам, давайте перейдем к написанию наших собственных пользовательских элементов.
Создать новые теги с пользовательскими элементами
Для создания совершенно новых элементов Aurelia использует очень похожий подход к пользовательским атрибутам. В качестве примера мы собираемся перестроить посты на странице gif, которые будут представлены пользовательским элементом reddit-gif
и предоставить возможность включать и выключать реальное видео. Результирующая разметка для нашего представления должна быть такой:
<require from="./reddit-gif"></require> ... <ul class="list-group"> <li class="list-group-item" repeat.for="p of posts"> <reddit-gif data.bind="p.data"></reddit-gif> </li> </ul>
Как видите, мы используем новый тег и предоставляем необходимую информацию через привязки data
свойству data
.
Следующим шагом является создание фактического элемента. Мы делаем это путем создания представления элемента reddit-gif.html
и его виртуальной машины reddit-gif.js
в папке src
. Представление, показанное далее, использует предыдущую разметку из gifs.html
и добавляет кнопку, которая переключает iframe, который используется для встраивания реального видео. Опять же, взгляды Аурелии заключены в тег шаблона:
<template> <button click.delegate="toggleGif()">Toggle Gif</button> <br /> <img src.bind="data.thumbnail == undefined ? '' : data.thumbnail" /> <a href="http://reddit.com${data.permalink}"> ${data.title} </a> <br /> <iframe class="reddit-gif" show.bind="gifActive" src.bind="gifSrc"></iframe> </template>
Рассматривая часть VM, мы следуем тому же процессу, что и при создании пользовательского атрибута. Но на этот раз мы используем другой декоратор, который сообщит Aurelia, что мы собираемся создать customElement
только с одним свойством с именем data
.
import {customElement, bindable} from 'aurelia-framework'; @customElement('reddit-gif') @bindable('data') export class RedditGif { ...
Далее мы определяем элемент gifActive
чтобы отслеживать, должен ли отображаться iframe. Мы также изначально устанавливаем член gifSrc
пустым, чтобы не загружать предварительно содержимое, если iframe невидим.
constructor() { this.gifActive = false; } bind() { this.gifSrc = ''; }
И последнее, но не менее важное: мы добавляем функцию toggleGif
используемую кнопкой переключения, которая отображает видимость и источник при каждом вызове.
toggleGif() { if(this.gifActive) { this.gifSrc = ''; } else { this.gifSrc = this.data.url + '#embed'; } this.gifActive = !this.gifActive; }
Вы можете просмотреть полный файл HTML здесь и файл JS здесь
Сокращение суммы кода с условными обозначениями
Aurelia — это все, чтобы опыт разработчика был как можно более приятным. Посмотрим правде в глаза: многие из нас не любят много печатать. Таким образом, чтобы сэкономить некоторые ценные нажатия клавиш и улучшить обслуживание с течением времени, Aurelia использует ряд простых соглашений. Например, полная версия bindable
декоратора на самом деле может выглядеть так, что мы обошли, просто предоставив имя свойства. Все остальные параметры будут автоматически определены.
@bindable({ name:'myProperty', //name of the property on the class attribute:'my-property', //name of the attribute in HTML changeHandler:'myPropertyChanged', //name of the method to invoke when the property changes defaultBindingMode: ONE_WAY, //default binding mode used with the .bind command defaultValue: undefined //default value of the property, if not bound or set in HTML })
Еще одна вещь, на которую стоит обратить внимание — как сократить использование нескольких свойств. Таким образом, вместо определения каждого из них по одному, мы также можем указать нашему пользовательскому атрибуту ожидать динамические свойства. Для этого мы украшаем наш класс с помощью dynamicOptions
декоратора. Теперь мы все еще можем повторно использовать ту же разметку представления, но не должны вручную определять все объявления свойств, что, как следует из названия, весьма полезно в динамических контекстах. Это означает, что мы можем написать один общий обработчик изменений с именем dynamicPropertyChanged
, который dynamicPropertyChanged
всякий раз, когда изменяется любое связанное свойство.
import {customAttribute, dynamicOptions, inject} from 'aurelia-framework'; import $ from 'bootstrap'; import bootstrap from 'bootstrap'; @inject(Element) @customAttribute('popover') @dynamicOptions export class Popover { constructor(element) { // store it for later use this.element = element; } bind() { $(this.element).popover({ title: this.title, placement: this.placement, content: this.content, trigger: 'hover' }); } dynamicPropertyChanged(name, newValue, oldValue) { $(this.element).data('bs.popover').options[name] = newValue; } }
Но как насчет пользовательских элементов? Ну, мы уже неявно использовали некоторые соглашения, даже не осознавая этого. Система автоматически объединяет пары View и VM, просто имея одно и то же имя. Если вам нужно использовать другое представление, вы можете использовать декоратор @useView(relativePath)
. Или, может быть, вообще не использовать представление, объявив @noView
. Мы даже можем сойти с ума и позволить нашему представлению отображаться в ShadowDOM, добавив декоратор useShadowDOM
. Если вы не знакомы с этим термином, пожалуйста, посмотрите на эту статью
Вывод
Мы, команда Aurelia, надеемся дать вам краткий обзор того, как расширить сам HTML, используя пользовательские элементы и атрибуты. Во всех примерах мы надеемся, что вы смогли увидеть наше внимание к опыту разработчиков, предложив вам гибкую, но простую в использовании среду, которая не мешает вам и не заставляет вас использовать странный API. Мы хотели бы пригласить вас присоединиться к нашему каналу Gitter, если у вас есть какие-либо вопросы. Мы также хотели бы услышать о вашем опыте, когда вы пишете свои первые пользовательские элементы и атрибуты.