Статьи

Создание приложения книжного клуба с AngularJS, Stripe и Stamplay

Эта статья была спонсирована Stamplay . Спасибо за поддержку спонсоров, которые делают возможным использование SitePoint.

Как разработчик внешнего интерфейса я часто создаю богатый пользовательский интерфейс для своих сторонних проектов с такими фреймворками, как Angular и React, но наступает момент, когда вашему приложению требуются данные, постоянство, бизнес-логика, электронная почта и целый ряд других поведений, которые обычно являются домен back-end разработчиков. Stamplay — это сервис, цель которого сделать эти аспекты разработки приложений такими же простыми, как заполнение формы.

Давайте конкретизируем простое приложение Book Club, создав для него «бэкэнд» с помощью Stamplay. Пользователи смогут оставлять отзывы с оценкой прочитанных книг. Они также могут оценить другие отзывы. Мы будем взимать плату с пользователей за доступ к книжному клубу и отправим им приветственный пакет при регистрации.

Начиная

Я уже создал оболочку внешнего интерфейса для приложения Book Club. По мере прохождения этого урока мы будем заполнять пробелы с помощью Stamplay.

Для начала вам необходимо клонировать следующее хранилище :

git clone [email protected]:bradbarrow/sp-stamplay.git 

Внутри каталога проекта проверьте ветку starter :

 git checkout starter 

Затем выполните следующее:

 bower install 

Это установит среди прочего:

  • AngularJS
  • Stamplay SDK
  • Bootstrap CSS
  • Угловой интерфейс Bootstrap
  • IonIcons
  • Algolia Search Client

Мы также включили JavaScript-клиент Stripe.

Чтобы запустить приложение, вам нужно установить http-сервер. Мне нравится использовать lr-http-server который вы можете установить, запустив:

 npm install -g lr-http-server 

Затем в каталоге вашего проекта просто запустите lr-http-server -p 8080 .

Настройка Stamplay

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

Создание нашего первого приложения

В редакторе штампов дайте новому приложению уникальное имя, а затем нажмите кнопку «Создать». Мы назвали наше приложение книжным клубом

Создание нового приложения

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

Stamplay CLI

Чтобы работать со Stamplay, нам нужно подключить наше приложение к API Stamplay. Stamplay предоставил пакет npm для этой цели. Идите вперед и установите пакет stamplay-cli .

 npm install -g stamplay-cli 

Теперь вы можете запустить stamplay init в каталоге вашего проекта, чтобы сгенерировать файл stamplay.json .

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

Книжный клуб нуждается в книгах

Нам понадобятся книги, если у нас будет книжный клуб. Наше приложение уже имеет список книг в index.html и BooksController в scripts/app.js Давайте перейдем к Stamplay и настроим наш Book CustomObject, прежде чем встраивать его в наше приложение.

На панели инструментов вашего приложения нажмите ссылку «Объект» в меню слева и нажмите « + Добавить» . Введите book в поле Object Name и нажмите enter чтобы начать заполнять его свойства.

Создание пользовательского объекта книги

Мы просто добавим одно строковое свойство с названием «title».

Добавление свойств заголовка к объекту книги

Попробуйте консоль API Stamplay

Stamplay имеет консоль API, которая помогает вам взаимодействовать с вашим приложением. Это позволяет вам выполнять действия API для получения / установки данных и видеть, как ваше приложение реагирует.

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

Нажмите на API Console в левом меню редактора Stamplay.

В меню «Операция» выберите «Создать объект».

В поле URL API выберите «книгу» из выпадающего списка.

Создание консоли Object API

Появится форма с запросом на название книги, которую вы хотите добавить. Давайте добавим «Убить насмешливую птицу». Нажмите кнопку Отправить.

Мы увидим запрос по мере его отправки в API вашего приложения и в итоге получим ответ. Все идет хорошо, это должно быть 200 ОК.

Давайте изменим нашу Операцию на «Получить все объекты» и снова выберем «Забронировать». Нажмите еще раз, и мы должны получить ответ «Убить насмешливую птицу».

Теперь пришло время добавить эти данные в наш интерфейс.

Проводка книг в нашем интерфейсе

Откройте scripts/app.js В самом верху файла добавьте следующую строку:

 Stamplay.init('YOURAPPID'); 

Это использует глобальный Stamplay из Stamplay SDK, который мы включили в index.html . Функция init идентифицирует наше приложение, чтобы остальные наши вызовы перешли к правильному приложению.

Затем мы создадим сервис Book для получения наших книг из Stamplay. Обновите app.js следующим образом:

 Stamplay.init("bookclub"); var app = angular.module('stamplay', ['ngStamplay']); app.controller('BooksController', function($scope, $rootScope, $stamplay, Book){ $scope.books = []; Book.all().then(function(books){ $scope.books = books; }); }); app.factory('Book', function($q, $stamplay){ function all() { var deferred = $q.defer(); var BookCollection = $stamplay.Cobject('book').Collection; BookCollection.fetch().then(function() { deferred.resolve(BookCollection.instance); }); return deferred.promise; } return { all: all } }); 

Вы заметите, что мы используем $stamplay здесь. Это доступно, так как мы включили модуль ngStamplay .

Здесь мы используем Angular Stamplay SDK для получения нашей коллекции книг. Мы создали простой сервис Book с методом all() .

Метод all внутренне вызывает fetch() для коллекции книг Stamplay, которая возвращает обещание . Как только это решит, BookCollection будет заполнен. (Помните, что модели и коллекции Stamplay по сути являются структурами Backbone.js .

В BooksController мы просто внедряем наш сервис и вызываем метод all() для заполнения массива books в области видимости.

В index.html нам нужно изменить строку {{book.title}} на {{book.instance.title}} в соответствии со структурой данных Stamplay. Вы также можете вызвать book.get (‘title’).

Теперь вы должны увидеть «Убить пересмешника» в списке книг, когда вы просматриваете приложение в браузере.

Добавление новых книг

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

 <div class="panel panel-default" ng-controller="BooksController"> <div class="panel-heading"> Books </div> <div class="panel-body"> <form class="form-horizontal" ng-submit="addBook()"> <div class="form-group"> <label for="title" class="col-sm-2 control-label">Book Title</label> <div class="col-sm-10"> <input type="text" ng-model="newBook.title" class="form-control" id="title" placeholder="The Lord of the Rings" autocomplete="off"> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default">Add Book</button> </div> </div> </form> </div> <div class="list-group"> <div class="list-group-item" ng-repeat="book in books"> {{book.instance.title}} </div> </div> </div> 

Затем мы добавим новый метод в наш сервис Book под названием add :

 app.factory('Book', function($q, $stamplay){ function all() { ... } function add(book) { var deferred = $q.defer(); var BookModel = $stamplay.Cobject('book').Model; BookModel.set('title', book.title); BookModel.save().then(function() { deferred.resolve(BookModel); }); return deferred.promise; } return { all: all, add: add } }); 

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

Осталось только, чтобы наш BooksController обработал отправку формы:

 app.controller('BooksController', function($scope, $rootScope, $stamplay, Book){ ... $scope.newBook = { title: '' }; // Empty book for form $scope.addBook = function() { Book.add($scope.newBook).then(function(savedBook){ $scope.books.push(savedBook); // Immediate UI response }); $scope.newBook.title = ''; // Blank out the form } }); 

Если вы заполните свою форму, вы увидите, что ваша книга добавлена ​​в список. Обновите страницу, и она все еще должна быть там. Мы только что добавили сохранение в наше приложение с помощью Stamplay. Полегче, а?

Разрешение пользователям зарегистрироваться / войти

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

Stamplay облегчает вход в систему. Прежде всего, нажмите «Пользователи», а затем «Аутентификация» в левом меню редактора штампов.

Здесь вы можете выбрать один из ряда решений по аутентификации для вашего приложения Stamplay. Сегодня мы будем использовать Google Plus.

Вы можете найти инструкции для этого процесса здесь . Это очень просто и займет всего несколько минут.

Получив идентификатор и секретный ключ приложения Google Plus, щелкните логотип Google Plus в разделе аутентификации Stamplay и введите свои данные, затем нажмите «Сохранить».

Использование аутентификации Google+

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

Чтобы установить общие права доступа, нажмите «Разрешения» в меню «Пользователь», затем перейдите на вкладку «Общие».

Публичные разрешения

Реализация Auth в нашем приложении

Теперь, когда мы подключились к Google Plus, вход в систему тривиален.

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

 app.factory('User', function($q, $stamplay){ function login() { var deferred = $q.defer(); var User = $stamplay.User().Model; User.login('google').then(function(){ deferred.resolve(User); }); } function active() { var deferred = $q.defer(); var User = $stamplay.User().Model; User.currentUser().then(function() { deferred.resolve(User); }).catch(function(err) { deferred.reject(err); }); return deferred.promise; } function logout() { var User = $stamplay.User().Model; User.logout(); } return { active: active, logout: logout, login: login }; }); 

Просто позвонив User.login('google') , Stamplay отправит наших пользователей через процесс OAuth, прежде чем вернуть их вошедшим в систему.

Мы собираемся добавить ссылку для входа в наш NavBar, но сначала давайте создадим NavController для обработки действий:

 app.controller('NavController', function($scope, User, $rootScope){ $scope.login = function(){ User.login().then(function(user){ // Add their details to root scope $rootScope.$emit('User::loggedIn', {user: user}); }); } $scope.logout = function(){ User.logout(); } }); 

После успешного входа мы отправляем событие с данными пользователя. Давайте настроим слушателя для этого события:

 app.run(function($rootScope, User){ // Listen for login events $rootScope.$on('User::loggedIn', function(event, data){ $rootScope.loggedIn = true; $rootScope.user = data.user; }); // Check if there's a user logged in already User.active().then(function(activeUser){ if(activeUser.isLogged()){ // Add their details to rootScope $rootScope.$emit('User::loggedIn', {user: activeUser}); } }); }); 

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

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

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

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

 <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-6" ng-controller="NavController"> <ul class="nav navbar-nav"> <li class="active"><a ng-href="#">Books</a></li> <li> <a ng-href="#" ng-show="!loggedIn" ng-click="login()"> Login </a> </li> <li> <a ng-href="#" ng-show="loggedIn" ng-click="logout()"> Logout {{user.instance.displayName}} </a> </li> </ul> </div> из <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-6" ng-controller="NavController"> <ul class="nav navbar-nav"> <li class="active"><a ng-href="#">Books</a></li> <li> <a ng-href="#" ng-show="!loggedIn" ng-click="login()"> Login </a> </li> <li> <a ng-href="#" ng-show="loggedIn" ng-click="logout()"> Logout {{user.instance.displayName}} </a> </li> </ul> </div> 

Если вы откроете это в своем браузере и попробуете, первое, что вы заметите, это то, что, щелкнув по ссылке, вы попадете на http://localhost/auth/v1/google/connect или что-то подобное. Это не будет работать, поскольку Google (по соображениям безопасности) ожидает, что реальный URL будет обрабатывать аутентификацию. К счастью, Stamplay позволяет легко развернуть наше приложение по реальному URL.

Просто запустите stamplay deploy в каталоге проекта.

Когда все будет готово, вы сможете увидеть свое приложение в прямом эфире по адресу http://yourappid.stamplayapp.com . Процесс входа в систему / выхода из системы теперь также должен работать.

Наконец, мы будем показывать форму «Добавить книгу» только когда пользователи вошли в систему:

 <div class="panel-body" ng-show="loggedIn"> <form class="form-horizontal" ng-submit="addBook()"> ... </form> </div> 

Отправка электронной почты

Давайте отправим приветственное письмо новым пользователям. Нажмите «Управление» в разделе «Задачи» в левом меню «Печать», затем нажмите «Новая задача». Мы собираемся выбрать: «Когда пользователь регистрируется, электронная почта — отправить электронную почту»

Отправка электронной почты

Нажмите «Продолжить», чтобы перейти к шагу 3, где вы можете использовать значения справа для заполнения своей электронной почты.

Форма электронной почты

Снова «продолжить», назовите вашу задачу и все. Когда новые пользователи зарегистрируются, они теперь получат от вас письмо 🙂

Создание рецензий на книги

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

Из редактора Stamplay вернемся на вкладку Objects и добавим новый пользовательский объект под названием «review»:

Добавьте строковое свойство с именем «text», которое будет содержать содержание отзывов.

Создание пользовательского объекта обзора

Теперь перейдите к объекту книги в редакторе Stamplay. Мы собираемся добавить поле к объекту книги, который представляет собой массив рецензий.

Создайте новое поле в объекте книги под названием «Обзоры» и выберите «Отношение объекта» — «Обзор» для типа.

Ассоциирование обзора

Форма для отзывов в нашем приложении

Теперь, когда мы настроили обзоры на Stamplay, нам нужно добавить возможность писать отзывы в наше приложение.

Для начала давайте создадим сервис для решения некоторых задач наших Обзоров:

 app.factory('Review', function($q, $stamplay, Book, $rootScope){ function all() { var deferred = $q.defer(); var ReviewCollection = $stamplay.Cobject('review').Collection; ReviewCollection.fetch().then(function() { deferred.resolve(ReviewCollection.instance); }); return deferred.promise; } function add(review) { var deferred = $q.defer(); var ReviewModel = $stamplay.Cobject('review').Model; ReviewModel.set('text', review.text); // The review text ReviewModel.set('owner', $rootScope.user.instance.id); //Associate with logged in user // Save the review ReviewModel.save().then(function() { // If it saves, update the book Book.find(review.bookId).then(function(BookToUpdate){ // Store the saved review on the book var currentReviews = BookToUpdate.get('reviews') || []; currentReviews.push(ReviewModel.get('_id')); BookToUpdate.set('reviews', currentReviews) BookToUpdate.save().then(function(){ // We're done deferred.resolve(ReviewModel); }); }); }); return deferred.promise; } return { all: all, add: add, } }); 

Что здесь важно:

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

Нам нужно добавить метод find() в наш сервис Book:

 function find(id) { var deferred = $q.defer(); var BookModel = $stamplay.Cobject('book').Model; BookModel.fetch(id).then(function() { deferred.resolve(BookModel); }).catch(function(err) { deferred.reject(err); }); return deferred.promise; } 

Затем добавьте это в экспорт для вашего сервиса:

 return { all: all, add: add, find: find // Now we can use Book.find() } 

Метод fetch() принимает идентификатор для поиска.

Теперь, когда у нас есть сервис для работы с нашими отзывами, давайте создадим контроллер для нашей формы:

 app.controller('ReviewController', function($scope, Book, $rootScope, Review){ $scope.bookOptions = []; Book.all().then(function(books){ $scope.bookOptions = books; }); $scope.newReview = { bookId: null, text: '', }; $scope.leaveReview = function() { Review.add($scope.newReview).then(function(savedReview){ $rootScope.$emit('Review::added', {review: savedReview}); $scope.newReview.text = ''; $scope.newReview.bookId = null; }); } }); 

Есть несколько вещей, которые нужно отметить в этом контроллере. Прежде всего, мы получаем список книг для отображения в раскрывающемся списке, чтобы пользователи могли выбирать, какую книгу они будут просматривать. Когда пользователи оставляют отзыв, мы будем использовать сервис «Обзор», чтобы добавить его, а когда он будет сделан, мы отправим событие, чтобы уведомить остальную часть нашего приложения.

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

 <div class="row" ng-show="loggedIn"> <div class="col-md-12"> <div class="panel panel-default" ng-controller="ReviewController"> <div class="panel-heading"> Add a review </div> <div class="panel-body" ng-show="loggedIn"> <form class="form-horizontal" ng-submit="leaveReview()"> <div class="form-group"> <label for="book" class="col-sm-2 control-label">Book</label> <div class="col-sm-10"> <select ng-model="newReview.bookId" ng-options="book.instance.id as book.instance.title for book in bookOptions" class="form-control" id="book" autocomplete="off"> <option value="">-- Choose a book --</option> </select> </div> </div> <div class="form-group"> <label for="text" class="col-sm-2 control-label">Review</label> <div class="col-sm-10"> <input type="text" ng-model="newReview.text" class="form-control" id="text" placeholder="I thought it was hilarious!" autocomplete="off"> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default">Leave Review</button> </div> </div> </form> </div> </div> </div> </div> 

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

Прежде всего, когда мы выбираем наши книги, мы должны сказать Stamplay, что мы также хотим, чтобы все связанные объекты (обзоры) были в ответе. Для этого мы указываем метод {populate: true} при получении наших книг. Обновите свой книжный сервис следующим образом:

 app.factory('Book', function($q, $stamplay){ function all() { var deferred = $q.defer(); var BookCollection = $stamplay.Cobject('book').Collection; BookCollection.fetch({populate: true}).then(function() { deferred.resolve(BookCollection.instance); }); return deferred.promise; } ... }); 

Теперь, в нашем BooksController, после загрузки книг у нас также будет доступ к обзорам каждой книги.

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

 app.controller('BooksController', function($scope, $rootScope, $stamplay, Book, Review, User){ $scope.books = []; var loadBooks = function(){ Book.all().then(function(books){ $scope.books = books; }); } $scope.newBook = { title: '' }; $scope.addBook = function() { Book.add($scope.newBook).then(function(savedBook){ $scope.books.push(savedBook); // Emit new book was added $rootScope.$emit('Book::added'); }); $scope.newBook.title = ''; } $rootScope.$on('Book::added', function(data){ loadBooks(); }); $rootScope.$on('Review::added', function(data){ loadBooks(); }); loadBooks(); }); . app.controller('BooksController', function($scope, $rootScope, $stamplay, Book, Review, User){ $scope.books = []; var loadBooks = function(){ Book.all().then(function(books){ $scope.books = books; }); } $scope.newBook = { title: '' }; $scope.addBook = function() { Book.add($scope.newBook).then(function(savedBook){ $scope.books.push(savedBook); // Emit new book was added $rootScope.$emit('Book::added'); }); $scope.newBook.title = ''; } $rootScope.$on('Book::added', function(data){ loadBooks(); }); $rootScope.$on('Review::added', function(data){ loadBooks(); }); loadBooks(); }); 

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

 <div class="list-group" ng-repeat="book in books"> <div class="list-group-item"> <h4 class="list-group-item-heading">{{book.instance.title}}</h4> </div> <div class="list-group-item" ng-repeat="review in book.instance.reviews"> {{review.text}} </div> </div> 

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

Отлично, теперь осталось сделать только одно — было бы неплохо показать имя пользователя с его рецензией.

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

Сначала нам понадобится метод find в нашем сервисе User:

 app.factory('User', function($q, $stamplay){ function find(id) { var deferred = $q.defer(); var User = $stamplay.User().Model; User.fetch(id).then(function() { deferred.resolve(User); }).catch(function(err) { deferred.reject(err); }); return deferred.promise; } ... }); 

Добавьте это к экспорту для вашего сервиса:

 return { active: active, logout: logout, login: login, find: find }; 

Затем мы будем использовать его в BooksController:

 app.controller('BooksController', function($scope, $rootScope, $stamplay, Book, Review, User){ $scope.books = []; var loadBooks = function(){ Book.all().then(function(books){ $scope.books = books; $scope.books.forEach(function(book){ var reviews = book.instance.reviews || []; reviews.forEach(function(review){ if(review.owner){ User.find(review.owner).then(function(user){ review.user = user.get('displayName'); }); } else { review.user = 'Anonymous'; } }); }) }); } ... }); 

Мы будем показывать это новое свойство пользователя перед каждым обзором:

 <div class="list-group-item" ng-repeat="review in book.instance.reviews"> <strong>{{review.user}}</strong> {{review.text}} </div> 

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

Сейчас самое время развернуть и протестировать ваше приложение с несколькими различными учетными записями.

Больше Stamplay Integrations

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

Добавить оценки в наши отзывы

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

 <div class="form-group"> <label for="text" class="col-sm-2 control-label">Rating</label> <div class="col-sm-10"> <input type="number" ng-model="newReview.rating" class="form-control" id="text" ng-minlength="1" ng-maxlength="5" placeholder="Rating out of 5" autocomplete="off"> </div> </div> 

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

 // Save the review ReviewModel.save().then(function() { // If it saves, update the book Book.find(review.bookId).then(function(BookToUpdate){ // Rate it BookToUpdate.rate(review.rating); // Store the saved review on the book var currentReviews = BookToUpdate.get('reviews') || []; currentReviews.push(ReviewModel.get('_id')); BookToUpdate.set('reviews', currentReviews) BookToUpdate.save().then(function(){ // We're done deferred.resolve(ReviewModel); }); }); }); 

Теперь мы можем отобразить эту дополнительную информацию (в виде звездочек) в нашем представлении, используя объект действий:

 <div class="list-group" ng-repeat="book in books"> <div class="list-group-item"> <h4 class="list-group-item-heading">{{book.instance.title}}</h4> <span ng-repeat="n in [1,2,3,4,5]"> <i class="icon ion-ios-star" ng-if="book.instance.actions.ratings.avg >= n"></i> <i class="icon ion-ios-star-outline" ng-if="book.instance.actions.ratings.avg < n"></i> </span> </div> <div class="list-group-item" ng-repeat="review in book.instance.reviews"> <strong>{{review.user}}</strong> {{review.text}} </div> </div> 

Добавить голоса в ваши отзывы

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

Давайте расширим наш сервис отзывов, чтобы он мог публиковать отзывы

 app.factory('Review', function($q, $stamplay, Book){ function all() { ... } function upvote(review) { var deferred = $q.defer(); var ReviewModel = $stamplay.Cobject('review').Model; ReviewModel.fetch(review.id).then(function(){ ReviewModel.upVote().then(function(){ deferred.resolve(ReviewModel); }); }).catch(function(err){ deferred.resolve(err); }); return deferred.promise; } }); 

Затем добавьте:

 return { all: all, add: add, upvote: upvote } 

Мы добавим кнопку к каждому обзору, которая позволяет upvote:

 <div class="list-group-item" ng-repeat="review in book.instance.reviews"> <button class="btn-default btn btn-xs" ng-click="upvote(review)"> {{review.actions.votes.total}} <i class="icon ion-arrow-up-a"></i> </button> &nbsp; <strong>{{review.user}}</strong> {{review.text}} </div> 

Затем мы добавим метод upvote() в наш BooksController, чтобы сохранить upvote

   $scope.upvote = function(review){ Review.upvote(review).then(function(){ $rootScope.$emit('Review::upvoted'); }); } $rootScope.$on('Review::upvoted', function(data){ loadBooks(); }); .   $scope.upvote = function(review){ Review.upvote(review).then(function(){ $rootScope.$emit('Review::upvoted'); }); } $rootScope.$on('Review::upvoted', function(data){ loadBooks(); }); 

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

Соединительная полоса

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

Сначала нам нужно настроить компонент Stripe. В меню задач в редакторе штампов нажмите «Компоненты», затем щелкните значок «Полоса».

Компонентное меню

Нажмите большую зеленую кнопку подключения, и вам будет предложено войти в свою учетную запись Stripe. Если у вас его нет, вы можете создать его на stripe.com . Вам нужно будет ввести свои банковские реквизиты (чтобы люди платили вам), хотя в этом руководстве мы будем использовать только тестовый режим.

Следуйте инструкциям для входа и подключения вашей учетной записи Stripe.

Когда вы закончите, вы должны увидеть зеленую кнопку с надписью «Компонент активирован»

Активация компонента

Вернувшись на страницу компонента Stripe, вы должны увидеть свои данные (тестовые ключи и т. Д.). Убедитесь, что живой режим отключен.

Теперь нам нужно создать задачу, чтобы при регистрации пользователей мы создавали для них новых клиентов Stripe. Нажмите «Управление» в меню задач, затем нажмите «Новая задача».

Из выпадающих списков мы выберем «Когда пользователь регистрируется», а затем «Stripe — Add customer».

Добавление клиента

Нажмите Далее, на шаге 3 убедитесь, что вы передаете {{user._id}} .

Нажмите «Далее» и дайте вашей задаче имя, например «Создать клиента», затем нажмите «Сохранить».

Теперь, когда пользователи зарегистрируются, у нас будет новый Заказчик, созданный в Stripe.

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

Прежде всего, мы сначала установим переменную paid в false и будем использовать ее, чтобы скрыть приложение, пока пользователь не заплатит. Мы также установим атрибут, paid для вошедшего в систему пользователя позже, поэтому давайте проверим это и здесь:

 app.run(function($rootScope, User){ $rootScope.paid = false; // Listen for login events $rootScope.$on('User::loggedIn', function(event, data){ $rootScope.loggedIn = true; $rootScope.paid = data.user.instance.paid || false; // Has the user already paid? $rootScope.user = data.user; }); // Check if there's a user logged in already User.active().then(function(activeUser){ ... }); }); 

Там, где мы сейчас используем ng-show="loggedIn" давайте также добавим чек на оплату:

Например,

 <div class="panel-heading"> Books </div> <div class="panel-body" ng-show="loggedIn && paid"> <form class="form-horizontal" ng-submit="addBook()"> ... 

Мы создадим контроллер и форму для обработки платежей:

 <div class="row" ng-show="loggedIn && !paid"> <div class="col-md-12"> <div class="panel panel-default" ng-controller="PaymentController"> <div class="panel-heading"> Pay to subscribe </div> <div class="panel-body" ng-show="loggedIn"> <form class="form-horizontal" ng-submit="pay()"> <div class="form-group"> <label for="book" class="col-sm-2 control-label">Card Number</label> <div class="col-sm-10"> <input type="text" ng-model="card.number" class="form-control" id="text" autocomplete="off"> </div> </div> <div class="form-group"> <label for="book" class="col-sm-2 control-label">CVC</label> <div class="col-sm-10"> <input type="text" ng-model="card.cvc" class="form-control" id="text" autocomplete="off"> </div> </div> <div class="form-group"> <label for="book" class="col-sm-2 control-label">Expiry Month</label> <div class="col-sm-10"> <input type="text" ng-model="card.exp_month" class="form-control" id="text" placeholder="02" autocomplete="off"> </div> </div> <div class="form-group"> <label for="book" class="col-sm-2 control-label">Expiry Year</label> <div class="col-sm-10"> <input type="text" ng-model="card.exp_year" class="form-control" id="text" placeholder="2015" autocomplete="off"> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default">Pay</button> </div> </div> </form> </div> </div> </div> </div> 
 app.controller('PaymentController', function($scope, $rootScope, $stamplay, User){ Stripe.setPublishableKey('your_stripe_TEST_key'); $scope.card = { number: '', cvc: '', exp_month: '', exp_year: '' } $scope.pay = function(){ Stripe.card.createToken($scope.card, function(status, response){ if (response.error) { console.log('error', response.error); } else { var token = response.id; var customerStripe = new $stamplay.Stripe(); customerStripe.charge($rootScope.user.instance.id, token, 50, 'USD').then(function (response) { $scope.$apply(function(){ User.update($rootScope.user.instance.id, 'paid', true).then(function(){ $rootScope.paid = true; }); }) }, function(err){ console.log('error', err); }) } }); } }); 

В нашем контроллере мы используем JavaScript-клиент Stripe для получения токена для карты, а затем используем функцию Stripe в Stamplay для создания заряда. Наконец, мы обновляем атрибут, paid пользователю за постоянство. Нам нужно создать метод обновления для пользователя.

 function update(id, key, value) { var deferred = $q.defer(); var User = $stamplay.User().Model; User.fetch(id).then(function() { User.set(key, value); User.save().then(function(){ deferred.resolve(User); }); }).catch(function(err) { deferred.reject(err); }); return deferred.promise; } 

Чтобы протестировать реализацию Stripe, вы можете использовать тестовые значения, как показано ниже. Информацию о тестировании в Stripe смотрите в документации .

 n.4242 4242 4242 4242 date: 07/2020 cvc: 424 

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

Stamplay позволяет нам интегрироваться с Algolia, хостингом поиска. Для этого раздела нам потребуется учетная запись Algolia (есть бесплатная пробная версия ).

Алголия Поиск

В Stamplay нам нужно подключиться к Алголии. Перейдите на страницу компонентов и нажмите Algolia. Введите свои данные (доступно на вкладке учетных данных на панели управления Algolia) и нажмите кнопку подключения.

Полномочия Алголии

Нам нужно будет создать индекс в Алголии . Онлайн-приложение Algolia позволяет легко добавлять индексы и их учебники понятны.

Мы назовем наш индекс «книгами» — убедитесь, что в индексе нет данных (фиктивных данных) для начала.

Теперь мы можем добавить новое задание в Stamplay.

Из критериев выберите: При создании нового объекта отправьте данные в Алголию.

Подталкивая данные Алголии

На следующих страницах мы выберем Книги (объекты, которые мы хотим найти) и поместим их в наш указатель с именем books.

Мы будем индексировать свойство title как «title», а свойство «_id» как bookId:

Индекс Книги Задача

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

Теперь мы можем перейти в Алголию и выбрать, какие свойства сделать доступными для поиска. Вы можете настроить это на вкладке рейтинга вашего индекса книг. Мы сделаем атрибут заголовка доступным для поиска.

Создание заголовка для поиска

Давайте поменяем этот выпадающий список на typeahead. У нас уже есть поисковый клиент Algolia, включенный в index.html . Нам нужно будет включить его как угловой модуль:

 var app = angular.module('stamplay', ['ngStamplay', 'algoliasearch']); 

Мы будем использовать директиву типа Angular Bootstrap. У нас уже есть JS, включенный в index.html поэтому давайте включим его также в качестве модуля Angular:

 var app = angular.module('stamplay', ['ngStamplay', 'algoliasearch', 'ui.bootstrap']); 

Мы заменим старый выпадающий список для директивы typeahead:

 <div class="form-group"> <label for="book" class="col-sm-2 control-label">Book</label> <div class="col-sm-10"> <input type="text" ng-model="newReview.book" placeholder="Find a book" typeahead="book as book.title for book in findBook($viewValue)" typeahead-loading="loadingBooks" class="form-control"> <i ng-show="loadingBooks" class="glyphicon glyphicon-refresh"></i> </div> </div> 

Вы заметите, что typeahead покажет название книги в результатах. Значение (модель) будет сам объект книги. Отображаемый список является результатом функции findBook() . Давайте реализуем это сейчас:

 app.controller('ReviewController', function($scope, Book, $rootScope, Review, algolia, $q){ // Replace the following values by your ApplicationID and ApiKey. var client = algolia.Client('FKSLNDAL5R', 'b1c739979a51be636bf6d2eb4eee8243'); // Replace the following value by the name of the index you want to query. var index = client.initIndex('books'); $scope.findBook = function(value) { var deferred = $q.defer(); index.search(value, { hitsPerPage: 5 }).then(function(content) { if (content.query !== value) { // do not take out-dated answers into account return; } deferred.resolve(content.hits); }, function(content) { deferred.resolve([]); return []; }); return deferred.promise; }; $scope.newReview = { book: null, text: '', }; $scope.leaveReview = function() { Review.add($scope.newReview).then(function(savedReview){ $rootScope.$emit('Review::added', {review: savedReview}); $scope.newReview.text = ''; $scope.newReview.book = null; }); } }); 

Вы также заметите, что мы обновили newReview чтобы иметь свойство book вместо bookId так как наш typeahead собирается назначить целые объекты книги для модели. (Это связано с ограничением в директиве Bootstrap, связанным с презентационными значениями)

Нам нужно обновить наш сервис Review, чтобы получить свойство bookId из объекта книги Algolia:

 // Save the review ReviewModel.save().then(function() { // If it saves, update the book // Access bookId on review.book (an Algolia object) Book.find(review.book.bookId).then(function(BookToUpdate){ // Rate it BookToUpdate.rate(review.rating); // Store the saved review on the book var currentReviews = BookToUpdate.get('reviews') || []; currentReviews.push(ReviewModel.get('_id')); BookToUpdate.set('reviews', currentReviews) BookToUpdate.save().then(function(){ // We're done deferred.resolve(ReviewModel); }); }); }); 

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

Вывод

Вы можете просмотреть законченную версию этого приложения на bookclub.stamplayapp.com . Законченный код также доступен на мастере здесь .

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

Stamplay предоставил пользователям SitePoint эксклюзивный купон на бесплатное предоставление трехмесячного премиум-плана (стоимость $ 600). Код купона — STAMPLAY4SP, срок его действия истекает 30 июля.