Создание движка для форума с помощью Meteor и AngularJS
Меня часто просят создать небольшие HTML форумы для посетителей сайта и службы поддержки. Поэтому я решил рассказать о том, как это сделать с помощью Meteor и AngularJS:

Для начала установим Meteor! Откройте командную строку и вставьте следующую команду:
$ curl https://install.meteor.com/ | sh
Если же вы используете Windows, загрузите официальную программу установки Meteor.
Теперь создадим новое приложение Meteor.
$ meteor create forum
Откройте каталог forum и удалите автоматически сгенерированные файлы: forum.css, forum.html и forum.js.
$ cd forum
$ rm forum.css forum.html forum.js
Я хочу установить Angular-Meteor, удалив ненужные пакеты - blaze-html-templates, ecmascript, autopublish и insecure. Начиная с Meteor v1.2, нужно вручную добавить check, чтобы выполнить базовую санитизацию ввода:
$ meteor remove blaze-html-templates ecmascript autopublish insecure
$ meteor add angular check
Создадим каталоги client и views в корне проекта. Первый представляет собой специальный каталог Meteor, который содержит код, используемый только на стороне клиента. Хотя Meteor позволяет написать код, выполняемый как на стороне клиента, так и на стороне сервера. Это довольно хорошая идея отделить бизнес-логику по соображениям безопасности. Давайте поместим HTML-шаблоны для верстки страниц и каталогов AngularJS в папку views:
$ mkdir client
$ mkdir views
Продолжим реализацию форума для сайта HTML. Создайте файл index.html в корневом каталоге проекта и разместите приведенный ниже код внутри него:
<head>
<title>Angular-Meteor Forum</title>
</head>
<body ng-app="forum">
<div ng-include="'views/forum.html'"></div>
</body>
Это будет загрузочный модуль forum AngularJS, который еще не существует. Он включает в себя шаблон views/forum.htm, который также еще не существует. Давайте исправим это.
Для начала исправим модуль AngularJS. Создайте файл client/app.js и определите модуль:
angular.module('forum', ['angular-meteor']);
Далее создайте шаблон форума views/forum.html. Это будет общий макет, используемый всеми страницами:
<div class="forum">
FORUM
</div>
На этом этапе можно запустить приложение.
$ meteor
Откройте браузер и перейдите по адресу http://localhost:3000. Вы должны увидеть текст FORUM. Давайте создадим шаблон views/pages/topics.html для отображения тем HTML форума:
<div class="page">
<h1>Topics</h1>
<div ng-repeat="topic in topics">
<a ui-sref="topic({topicId: topic._id})">{{ topic.name }}</a>
</div>
</div>
Чтобы увидеть этот шаблон в действии, мы установим решение для маршрутизации в AngularJS - angular-ui-router, и определим основные маршруты. Выйдите из Meteor с помощью Ctrl+C или вновь откройте командную строку и перейдите к корневой папке приложения:
meteor add angularui:angular-ui-router
Созданный нами app.js - отличное место для настройки маршрутизации и контроллеров. Тем не менее, вы можете поместить их в разные файлы. Если сделаете это, то убедитесь, что Meteor загружает файлы. Поместите определение модуля в файл client/lib/, иначе он будет загружен после контроллера и каталога, вызывая исключение в AngularJS.
Замените содержимое файла app.js:
angular.module('forum', ['angular-meteor', 'ui.router'])
.config(function($urlRouterProvider, $stateProvider){
// Установка маршрута по умолчанию
$urlRouterProvider
.when('/', '/topics')
.otherwise('/topics');
// Добавление состояния
$stateProvider.state('topics', {
url: '/topics',
templateUrl: 'views/pages/topics.html'
});
})
.run(function($state){
// Мы вводим $state здесь, чтобы инициализировать ui.router
})
Прежде чем список тем отобразится в браузере, нужно доработать файл views/forum.html. Нам необходим элемент с атрибутом ui-view для отрисовки шаблона форума HTML, связанного с текущим маршрутом:
<div class="forum">
<div class="page-container" ui-view>
</div>
</div>
Перейдите сейчас по адресу http://localhost:3000 и обратите внимание, что вы мгновенно перенаправляетесь на страницу тем. Но у нас еще нет тем. Поскольку мы захотим добавить или удалить темы, будет хорошей идеей сохранить их в базе данных.
Meteor использует СУБД MongoDB. Можно определить коллекции MongoDB с помощью new Meteor.Collection (collectionName). Этот код должен присутствовать и на стороне клиента, и на стороне сервера, поэтому создадим каталог common и поместим данную строку в common/db.js:
Topics = new Meteor.Collection('Topics');
В Meteor публикация - это способ построения набора данных для отправки клиенту. Вы вызываете Meteor.publish с именем набора данных, и функция обратного вызова возвращает курсор в mongodb. Meteor считывает изменения из mongodb oplog (или опрашивает базу данных, если oplog не установлен должным образом) и обновляет курсор, когда данные добавлены, удалены или изменены.
Публикация должна быть определена в файле только для сервера, поэтому создадим папку server. Каждый JavaScript-файл, который вы помещаете сюда, будет работать только на сервере.
Создадим файл server/publications.js:
Meteor.publish('topics', function(){
return Topics.find();
});
Meteor.publish('topic', function(id){
check(id, String);
return Topics.find({_id: id});
});
Это позволит определить две публикации в форуме для сайта HTML: одну для всех тем и одну для той, которую запрашивает идентификатор. Кроме этого, давайте вставим некоторые темы по умолчанию, если коллекция пуста. Поместите приведенный ниже код в server/defaults.js:
if (Topics.find().count() === 0) {
_.each(['General Discussion', 'Tutorials', 'Help'], function(topicName){
Topics.insert({name: topicName});
});
}
Клиент инициирует подписку, которая подключается к публикации, и получает эти данные. Давайте посмотрим, как это сделать с помощью Angular-Meteor. В client/app.js добавьте параметр контроллера в определение состояния topics, называемого TopicsContoller, и определите контроллер после .run():
// ...
$stateProvider.state('topics', {
url: '/topics',
templateUrl: 'views/pages/topics.html',
controller: 'TopicsContoller'
});
// ...
.controller('TopicsContoller', function($scope){
$scope.subscribe('topics');
$scope.helpers({
topics: function() {
return Topics.find({}, {sort: {name:1}});
}
});
})
Здесь мы присоединились к публикации topics, а также добавили помощника с именем topics, который возвращает курсор MongoDB на список тем, отсортированных по именам. С точки зрения AngularJS это простой массив, доступный в переменной $scope.topics.
Просмотрите HTML форум в браузере. Вы должны увидеть три темы в соответствии с заголовком. Тем не менее, если кликнуть по ним, ничего не произойдет. Это потому, что мы не определили состояние topic. Давайте сделаем это и определим TopicController:
// ...
$stateProvider.state('topic', {
url: '/topic/:topicId',
templateUrl: 'views/pages/topic.html',
controller: 'TopicContoller'
});
// ...
.controller('TopicContoller', function($scope, $stateParams){
$scope.subscribe('topic', function(){ return [$stateParams.topicId]; });
$scope.helpers({
topic: function() {
return Topics.findOne({_id: $stateParams.topicId});
}
});
})
Это довольно просто, но учтите, что нужно использовать функцию, чтобы передать аргумент идентификатора topics публикации и вернуть одну тему с findOne вместо find.
Шаблон views/pages/topic.html будет довольно простым, но не волнуйтесь, мы скоро добавим список ветвей обсуждения:
<div class="page">
<h1>{{ topic.name }}</h1>
</div>
Чтобы использовать HTML код форума для сайта, нужно идентифицировать пользователей. У Meteor есть полезные пакеты для аутентификации:
- accounts-base: Этот пакет реализует основные функции, необходимые для учетных записей пользователей и позволяет другим пакетам использовать службы входа в систему;
- accounts-password: служба, которая включает безопасный, основанный на пароле, вход в систему;
- dotansimha:accounts-ui-angular: AngularJS «контейнер» для пакета UI учетной записи Meteor.
Давайте установим их:
$ meteor add accounts-base accounts-password dotansimha:accounts-ui-angular
Для использования dotansimha:accounts-ui-angular модуль AngularJS должен указать accounts.ui в качестве зависимости:
angular.module('forum', ['angular-meteor', 'ui.router', 'accounts.ui'])
Теперь можно добавить аутентификацию, регистрацию, функции: forgot password и change password с помощью одной строки. Лучше всего прописать все это в views/forum.html, поскольку это корневое расположение для всех страниц шаблона форума HTML:
<div class="forum">
<login-buttons></login-buttons>
<div ui-view>
</div>
</div>
Пора создать потоки выполнения:
- Определить коллекцию common/db.js:
Threads = new Meteor.Collection('Threads');
- Опубликовать треды (ветви обсуждения) в server/publications.js:
Meteor.publish('threads', function(topicId){
check(topicId, String);
return Threads.find({topicId: topicId});
});
Meteor.publish('thread', function(id){
check(id, String);
return Threads.find({_id: id});
});
- Отредактировать views/pages/topic.html, чтобы перечислить треды темы и добавить форму, где пользователи могут создавать новые ветви обсуждения:
<div class="page">
<h1>{{ topic.name }}</h1>
<ul>
<li ng-repeat="thread in threads">
<a ui-sref="thread({threadId: thread._id})">{{ thread.content }}</a>
by {{ thread.author }} at {{ thread.createdAt | date }}
</li>
</ul>
<h2>Create a new thread</h2>
<form ng-submit="createThread(thread)">
<input type="text" placeholder="Start discussion..." ng-model="thread.content">
<button type="submit">Create</button>
</form>
</div>
- Изменить TopicController в client/app.js так, чтобы он подписался к списку тредов, которые принадлежат текущей теме, и управлял созданием ветвей обсуждения:
.controller('TopicContoller', function($scope, $stateParams, $meteor){
$scope.subscribe('topic', function(){ return [$stateParams.topicId]; });
$scope.subscribe('threads', function(){ return [$stateParams.topicId]; });
$scope.helpers({
topic: function() {
return Topics.findOne({_id: $stateParams.topicId});
},
threads: function() {
return Threads.find({topicId: $stateParams.topicId});
}
});
$scope.createThread = function(thread){
$meteor.call("createThread", $stateParams.topicId, thread.content).then(function(){
thread.content = '';
}).catch(function(){
alert("An error occured while creating the thread!");
});
};
})
Увидели вложенную службу $meteor? Мы используем ее для вызова серверного метода createThread, который еще предстоит создать.
Вставьте приведенный ниже код в файл HTML форума server/methods.js:
Meteor.methods({
createThread: function(topicId, content){
check(topicId, String);
check(content, String);
var user = Meteor.user();
if (!user) {
throw new Meteor.Error("You are not logged in!");
}
if (!content){
throw new Meteor.Error("Content is required!");
}
var thread = {
author: user.emails[0].address,
createdAt: new Date(),
topicId: topicId,
content: content
};
return Threads.insert(thread);
}
});
Meteor.methods ожидает объект, в котором ключи - имена методов, а значения - определения методов. Методы могут иметь аргументы и могут возвратить что-либо, что может быть сериализовано в формате JSON. Курсоры Meteor не сериализуются, поэтому постарайтесь избежать возвращения collection.find(...), поскольку это приведет к краху приложения.
Давайте подведем итоги. Мы можем:
- Перечислить темы;
- Открыть темы со списком тредов;
- Создать треды;
- Зарегистрироваться / войти / выйти, сбросить забытый пароль и изменить пароль.
Что осталось сделать:
- Определить коллекцию Posts и опубликовать ее так же, как это сделали с Threads;
- Создать маршрут для одной темы, чтобы перечислить сообщения так же, как это сделали с topic;
- Создать форму, чтобы публиковать тему, как это сделали с topic;
- Написать метод для создания записи, как мы это сделали с createThread.
Вы готовы к реализации проекта? Не волнуйтесь, вы всегда можете посмотреть пример создания форума на сайте HTML в репозитории GitHub.
Пожалуйста, оставьте ваши комментарии по текущей теме статьи. За комментарии, подписки, дизлайки, отклики, лайки низкий вам поклон!