Russian (Pусский) translation by Sergey Zhuk (you can also view the original English article)
В предыдущей части серии мы загрузили приложение AngularJS, настроили маршрутизацию для разных представлений и построили службы вокруг маршрутов для сообщений, пользователей и категорий. Используя эти службы, мы теперь, наконец, можем извлекать данные с сервера для отображения их на фронтенде.
В этой части серии мы будем работать над созданием пользовательской директивы AngularJS для функции публикации сообщений. В текущей части серии мы будем:
- Познакомимся с директивами AngularJS и почему мы должны создавать их
- Запланируем директиву для функции публикации сообщений и аргументы, которые она примет
- Создадим пользовательскую директиву AngularJS для публикации сообщения вместе со своим шаблоном
Итак, начнем с введения в директивы AngularJS и почему они нам нужны.
Представление директив AngularJS
Директивы в AngularJS - это способ изменения поведения элементов HTML и повторного использования повторяющегося фрагмента кода. Они могут использоваться для изменения структуры HTML-элемента и его дочерних элементов, и, таким образом, это идеальный способ введения пользовательских виджетов интерфейса.
При анализе каркасов в первой части серии мы отметили, что функция публикации постов используется в трех отображениях, а именно:
- Листинг поста
- Профиль автора
- Список категорий сообщений
Поэтому вместо написания отдельных функций для отображения сообщений на всех этих трех страницах мы можем создать специальную директиву AngularJS, которая содержит бизнес-логику для извлечения записей с использованием служб, которые мы создали в более ранней части этой серии. Помимо бизнес-логики, эта директива будет также содержать логику рендеринга для отображения сообщений на определенных видах. Кроме того, в этой директиве будут определены функции для постраничной публикации и поиска сообщений по определенным критериям.
Следовательно, создание настраиваемой AngularJS-директивы для функции публикации записей позволяет нам определять функциональные возможности только в одном месте, и это облегчит нам в будущем расширять или модифицировать эту функциональность, не меняя код во всех трех случаях где он используется.
Сказав это, давайте начнем писать нашу собственную директиву для функции публикации сообщений.
Планирование пользовательской директивы AngularJS для публикации в сообщениях
Прежде чем мы начнем писать код для создания директивы для функции публикации сообщений, давайте проанализируем функциональные возможности, которые необходимы в этой директиве.
На самом базовом уровне нам нужна директива, которую мы могли бы использовать в наших представлениях для публикации сообщения, профиля автора и страницы категории. Это означает, что мы создадим пользовательский виджет пользовательского интерфейса (или маркер DOM), который мы разместим в нашем HTML, а AngularJS позаботится обо всем остальном в зависимости от того, какие опции мы предоставляем для этого конкретного экземпляра директивы.
Следовательно, мы создадим собственный виджет пользовательского интерфейса, идентифицированный следующим тегом:
<post-listing></post-listing>
Но нам также нужно, чтобы эта директива была гибкой, то есть принимала аргументы в качестве входных данных и действовала соответствующим образом. Рассмотрим страницу профиля пользователя, где нам нужны только сообщения, принадлежащие этому конкретному пользователю, или страница категории, в которой будут отображаться сообщения, относящиеся к этой категории. Эти аргументы могут быть представлены двумя способами:
- В URL-адресе в качестве параметров
- Прямо к директиве в качестве значения атрибута
Предоставление аргументов в URL кажется родным для API, поскольку мы уже знакомы с этим. Следовательно, пользователь может получить набор сообщений, принадлежащих конкретному пользователю, следующим образом:
https://127.0.0.1:8080/#/posts?author=1
Вышеупомянутые функциональные возможности могут быть достигнуты с помощью сервиса $routeParams
, предоставленного AngularJS. Здесь мы можем получить доступ к параметрам, предоставленным пользователем в URL-адресе. Мы уже рассмотрели его при регистрации маршрутов в предыдущей части серии.
Что касается предоставления аргументов непосредственно директиве в качестве значения атрибута, мы могли бы использовать что-то вроде следующего:
<post-listing post-args="{author=1}"></post-listing>
Атрибут post-args
в приведенном выше фрагменте принимает аргументы для извлечения определенного набора сообщений, и в настоящее время он принимает идентификатор автора. Этот атрибут может принимать любое количество аргументов для извлечения сообщений, поддерживаемых маршрутом /wp/v2/posts
. Таким образом, если бы мы получили набор сообщений, созданных пользователем, имеющим идентификатор 1 и принадлежащий к категории ID 10, мы могли бы сделать что-то вроде следующего:
<post-listing post-args="{author=1, filter[cat]=10}"></post-listing>
Параметр filter[cat]
в приведенном выше коде используется для извлечения набора сообщений, относящихся к определенной категории.
Разбиение на страницы также является важной функцией при работе со страницами для публикации сообщений. Директива будет обрабатывать постраничное разбиение на страницы, и эта функция будет зависеть от значений заголовков X-WP-Total
и X-WP-TotalPages
, возвращаемых сервером вместе с телом ответа. Следовательно, пользователь сможет перемещаться вперед и назад между предыдущими и следующими наборами сообщений.
Решив вкратце принять заказную директиву для публикации сообщений, у нас теперь есть достаточно прочная основа для начала написания кода.
Создание пользовательской директивы для публикации сообщений
Построение директивы для функции публикации записей включает в себя два шага:
- Создание бизнес-логики для извлечения записей и обработки других материалов.
- Создание представления для отображения этих сообщений на странице.
Бизнес-логика для нашей настраиваемой директивы будет обрабатываться в объявлении директивы. А для рендеринга данных в DOM мы создадим собственный шаблон для перечисления сообщений. Начнем с объявления директивы.
Объявление директивы
Директивы в AngularJS могут быть объявлены для модуля со следующим синтаксисом:
/** * Creating a custom directive for posts listing */ quiescentApp.directive( 'postListing', [function() { return { }; }] );
Здесь мы объявляем директиву на нашем модуле, используя метод .directive()
, доступный в модуле. Метод принимает имя директивы в качестве первого аргумента, и это имя тесно связано с именем тега элемента. Поскольку мы хотим, чтобы наш HTML-элемент был вида <post-listing></post-listing>
, мы предоставляем представление имени тега в camel-case формате. Вы можете узнать больше об этом процессе нормализации, выполняемом AngularJS, чтобы согласовать имена директив в официальной документации.
Обозначение, которое мы используем в приведенном выше коде для объявления нашей директивы, называется безопасным способом инъекции зависимостей. И в этой нотации мы предоставляем массив зависимостей в качестве второго аргумента, который потребуется директиве. В настоящее время мы не определили никаких зависимостей для нашей настраиваемой директивы. Но так как нам нужна служба Posts
для извлечения сообщений (которые мы создали в предыдущей части серии) и сервисы $routeParams
и $location
от AngularJS для доступа к параметрам URL и текущему пути, мы определяем их следующим образом:
/** * Creating a custom directive for posts listing */ quiescentApp.directive( 'postListing', ['$routeParams', '$location', 'Posts', function( $routeParams, $location, Posts ) { return { restrict: 'E', scope: { postArgs: '=' }, link: function( $scope, $elem, $attr ) { } }; }] );
Эти зависимости затем становятся доступными для функции, которая определена как последний элемент массива. Эта функция возвращает объект, содержащий определение директивы. В настоящее время у нас есть два свойства в объекте определения директивы: restrict
и link
.
Параметр restrict
определяет, как мы используем директиву в нашем коде, и для этого варианта могут быть четыре возможных значения:
-
A:
Для использования директивы в качестве атрибута существующего HTML-элемента. - E: Для использования директивы в качестве имени элемента.
-
C
: для использования директивы в качестве имени класса.
-
M
: Для использования директивы в качестве комментария HTML.
Параметр restrict
может также принимать любую комбинацию из четырех указанных выше значений.
Поскольку мы хотим, чтобы наша директива была новым элементом <post-listing></post-listing>
, мы устанавливаем параметр ограничения равным E
. Если мы должны были определить директиву, используя атрибуты на уже существующем HTML-элементе, то мы могли бы установить эту опцию в A
. В этом случае мы могли бы использовать <div post-listing></div>
, чтобы определить директиву в нашем HTML-коде.
Второе свойство scope
используется для изменения области действия директивы. По умолчанию значение свойства scope
равно false
, что означает, что область действия этой директивы такая же, как и у ее родителя. Когда мы передаем ему объект, для этой директивы создается изолированная область действия, и любые данные, которые необходимо передать директиве родительским элементом, передаются через атрибуты HTML. Это то, что мы делаем в нашем коде, и атрибут, который мы используем post-args
, который нормализуется в postArgs
.
Свойство postArgs
в объекте scope
может принимать любое из следующих трех значений:
-
=
: Означает, что значение, переданное в атрибут, будет рассматриваться как объект. -
@
: Означает, что значение, переданное в атрибут, будет рассматриваться как обычная строка.
-
&
: Значение, которое значение, переданное в атрибут, будет рассматриваться как функция.
Поскольку мы решили использовать значение =
, любое значение, которое будет передано в атрибут post-args
, будет рассматриваться как объект JSON, и мы могли бы использовать этот объект в качестве аргумента для извлечения сообщений.
Третье свойство, link
используется для определения функции, которая используется для управления DOM и определения API и функций, необходимых для этой директивы. Эта функция выполняет всю логику этой директивы.
Функция link
принимает аргументы для объекта scope, HTML-элемента директивы и объекта для атрибутов, определенных в HTML-элементе директивы. В настоящее время мы передаем два аргумента $scope
и $elem
для объекта scope и элемента HTML соответственно.
Определим некоторую переменную в свойстве $scope
, которую мы будем использовать для визуализации функции публикации записей в DOM.
/** * Creating a custom directive for posts listing */ quiescentApp.directive( 'postListing', ['$routeParams', '$location', 'Posts', function( $routeParams, $location, Posts ) { return { restrict: 'E', scope: { postArgs: '=' }, link: function( $scope, $elem, $attr ) { // defining variables on the $scope object $scope.posts = []; $scope.postHeaders = {}; $scope.currentPage = $routeParams.page ? Math.abs( $routeParams.page ) : 1; $scope.nextPage = null; $scope.previousPage = null; $scope.routeContext = $location.path(); } }; }] );
Мы определили шесть свойств объекта $scope
, к которым мы могли получить доступ в DOM. Этими свойствами являются:
-
$posts
: Массив для хранения объектов сообщений, которые будут возвращены сервером. -
$postHeaders
: объект для хранения заголовков, которые будут возвращены сервером вместе с телом ответа. Мы будем использовать их для управления навигацией.
-
$currentPage
: целочисленная переменная, содержащая номер текущей страницы.
-
$previousPage
: переменная, содержащая номер предыдущей страницы.
-
$nextPage
: переменная, содержащая номер следующей страницы.
-
$routeContext
: для доступа к текущему пути с помощью службы$location
.
Свойство postArgs
, которое мы определили ранее для атрибутов HTML, уже будет доступно в объекте $scope
внутри директивы.
Теперь мы готовы сделать запрос на сервер, используя службу Posts
для извлечения сообщений. Но до этого мы должны учитывать аргументы, предоставленные пользователем как параметры URL, а также параметры, предоставленные атрибутом post-args
. И для этой цели мы создадим функцию, которая использует службу $routeParams
для извлечения URL-параметров и объединяет их с аргументами, предоставляемыми атрибутом post-args
:
/** * Creating a custom directive for posts listing */ quiescentApp.directive( 'postListing', ['$routeParams', '$location', 'Posts', function( $routeParams, $location, Posts ) { return { restrict: 'E', scope: { postArgs: '=' }, link: function( $scope, $elem, $attr ) { // defining variables on the $scope object $scope.posts = []; $scope.postHeaders = {}; $scope.currentPage = $routeParams.page ? Math.abs( $routeParams.page ) : 1; $scope.nextPage = null; $scope.previousPage = null; $scope.routeContext = $location.path(); // preparing query arguments var prepareQueryArgs = function() { var tempParams = $routeParams; delete tempParams.id; return angular.merge( {}, $scope.postArgs, tempParams ); }; } }; }] );
Метод prepareQueryArgs()
в приведенном выше коде использует метод angular.merge()
, который расширяет объект $scope.postArgs
объектом $routeParams
. Но прежде чем объединить эти два объекта, он сначала удаляет свойство id
из объекта $routeParams
, используя оператор delete
. Это необходимо, так как мы будем использовать эту директиву в категориях и представлениях пользователей, и мы не хотим, чтобы идентификаторы категорий и пользователей ошибочно интерпретировались как идентификатор сообщения.
Подготовив аргументы запроса, мы, наконец, готовы сделать вызов на сервер и получить сообщения, и мы делаем это с помощью метода Posts.query()
, который принимает два аргумента:
- Объект, содержащий аргументы для создания запроса.
- Функция обратного вызова, которая выполняется после завершения запроса.
Поэтому мы будем использовать функцию prepareQueryArgs()
для подготовки объекта к аргументам запроса, а в функции обратного вызова мы устанавливаем значения определенных переменных в свойстве $scope
:
// make the request and query posts Posts.query( prepareQueryArgs(), function( data, headers ) { $scope.posts = data; $scope.postHeaders = headers(); $scope.previousPage = ( ( $scope.currentPage + 1 ) > $scope.postHeaders['x-wp-totalpages'] ) ? null : ( $scope.currentPage + 1 ); $scope.nextPage = ( ( $scope.currentPage - 1 ) > 0 ) ? ( $scope.currentPage - 1 ) : null; });
Функция обратного вызова получает два аргумента для тела ответа и заголовков ответа. Они представлены аргументами data
и headers
соответственно.
Аргумент headers
- это функция, которая возвращает сервер, содержащий заголовки ответа.
Остальной код довольно понятен, поскольку мы устанавливаем значение массива $scope.posts
. Для установки значений переменных $scope.previousPage
и $scope.nextPage
мы используем свойство x-wp-totalpages
в объекте postHeaders
.
И теперь мы готовы предоставить эти данные в интерфейсе с помощью настраиваемого шаблона для нашей директивы.
Создание настраиваемого шаблона для директивы
Последнее, что нам нужно сделать - отдельный шаблон для публикации сообщения и связать его с директивой. Для этого нам нужно изменить объявление директивы и включить свойство templateUrl
, например:
/** * Creating a custom directive for posts listing */ quiescentApp.directive( 'postListing', ['$routeParams', '$location', 'Posts', function( $routeParams, $location, Posts ) { return { restrict: 'E', scope: { postArgs: '=' }, templateUrl: 'views/directive-post-listing.html', link: function( $scope, $elem, $attr ) { } }; }] );
Это свойство templateUrl
в приведенном выше коде ссылается на файл с именем directive-post-listing.html в каталоге views. Поэтому создайте этот файл в папке views и вставьте следующий HTML-код:
<!-- post listing starts --> <article class="post-entry"> <h2 class="post-title"><a href="post-single.html">Good design is a lot like clear thinking made visual.</a></h2> <figure class="post-thumbnail"> <img src="img/img-712-348.jpg" alt="Featured Image"> </figure> <p class="post-meta"> By <a href="author.html">Bilal Shahid</a> in <a href="category.html">Quotes</a> </p> <div class="post-content"> <p>Created days forth. Dominion. Subdue very hath spirit us sixth fish creepeth also. First meat one forth above. You'll Fill for. Can't evening one lights won't. Great of make firmament image. Life his beginning blessed lesser meat spirit blessed seas created green great beginning can't doesn't void moving. Subdue evening make spirit lesser greater all living green firmament winged saw tree one divide wherein divided shall dry very lesser saw, earth the. Light their the.</p> </div> </article> <!-- post listing ends --> <!-- pagination links start --> <div class="post-pagination"> <a href="#" class="button">Older Posts</a> <a href="#" class="button">Newer Posts</a> </div> <!-- pagination links end -->
Это очень простой HTML-код, представляющий одну запись в сообщении и постраничную публикацию. Я скопировал его из файла views/listing.html. Мы будем использовать некоторые директивы AngularJS, в том числе ng-repeat
, ng-href
, ng-src
и ng-bind-html
, чтобы отобразить данные, которые в настоящее время находятся в свойстве $scope
директивы.
Измените код HTML следующим образом:
<!-- post listing starts --> <article class="post-entry" ng-repeat="post in posts"> <h2 class="post-title"><a ng-href="#/posts/{{post.slug}}">{{post.title.rendered}}</a></h2> <figure class="post-thumbnail" ng-show="post.quiescent_featured_image"> <img ng-src="{{post.quiescent_featured_image}}" alt="Featured Image"> </figure> <p class="post-meta"> By <a ng-href="#/users/{{post.author}}">{{post.quiescent_author_name}}</a> in <a ng-href="#/categories/{{category.term_id}}" ng-repeat="category in post.quiescent_categories">{{category.name}}{{$last ? '' : ', '}}</a> </p> <div class="post-content" ng-bind-html="post.excerpt.rendered"></div> </article> <!-- post listing ends -->
В приведенном выше коде используется директива ng-repeat
для итерации массива $scope.posts
. Любое свойство, которое определено в объекте $scope
в объявлении директивы, доступно непосредственно в шаблоне. Следовательно, мы ссылаемся на массив $scope.posts
непосредственно как posts
в шаблоне.
Используя директиву ng-repeat
, мы гарантируем, что контейнер article.post-entry
будет повторяться для каждого сообщения в массиве posts
, а каждое сообщение будет упоминаться как post
во внутреннем цикле. Этот объект post
содержит данные в формате JSON, возвращаемые сервером, содержащие такие свойства, как заголовок, почтовый идентификатор, постсодержание и ссылку на изображение, которое является дополнительным полем, добавленным плагином.
На следующем этапе мы заменяем такие значения, как заголовок сообщения, ссылка для публикации и ссылка для избранных изображений со свойствами объекта post
.
Для нумерации страниц замените предыдущий код следующим:
<!-- pagination links start --> <div class="post-pagination"> <a ng-href="#{{routeContext}}?page={{nextPage}}" class="button" ng-class="{'disabled': !nextPage}">Newer Posts</a> <a ng-href="#{{routeContext}}?page={{previousPage}}" class="button" ng-class="{'disabled': !previousPage}">Older Posts</a> </div> <!-- pagination links end -->
Сначала мы получаем доступ к свойству routeContext
, который мы определили в нашем объявлении директивы, и добавим его параметром ?page=
и используем значения nextPage
и previousPage
переменных для навигации вперед и назад между сообщениями. Мы также проверяем, не равна ли ссылка на следующую страницу или предыдущую страницу null
, иначе мы добавим класс .disabled
к кнопке, предоставленной Zurb Foundation.
Теперь, когда мы закончили эту директиву, пришло время ее протестировать. И мы делаем это, помещая тег <post-listing> </post-listing>
в наш HTML, идеально прямо над тегом <footer></footer>
. Это означает, что сообщение будет отображаться над нижним колонтитулом. Не беспокойтесь о форматировании и стилях, поскольку мы будем иметь дело с ними в следующей части серии.
Так что это в значительной степени все что нужно для создания настраиваемой AngularJS-директивы для функции публикации сообщений.
Что дальше?
В текущей части серии о создании интерфейса с WP REST API и AngularJS мы создали специальную директиву AngularJS для функции публикации сообщений. Эта директива использует службу Posts
, которую мы создали в более ранней части серии. Директива также принимает пользовательский ввод в виде атрибута HTML и через параметры URL.
В заключительной части серии мы начнем работу над заключительной частью нашего проекта, то есть контроллерами для сообщений, пользователей и категорий, и их соответствующими шаблонами.
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post