- PVSM.RU - https://www.pvsm.ru -
Эта статья предназначена для начинающих свой путь в разработку на NodeJS, и знакомит новичка с разработкой на этой платформе с использованием фреймворка SailsJS [1]. В статье, будет рассматриваться процесс разработки простого блога, с сопутствующими пояснительными материалами, цель которых описать начальные навыки работы с этим фреймворком — который безусловно является отличной основой для любых проектов на NodeJS. Для лучшего усвоения материала желательно иметь основные представления о языке программирования Javascript, и его серверной реализации NodeJS [2], а также как минимум первичное представление о схеме MVC [3] которая является основой Sails. Для лучшего понимания фреймворка вы можете почитать документацию [4] на официальном сайте Sails, а также посмотреть касты [5] описывающие работу с Sails достаточно подробно. При написании статьи я старался написать материал как можно более проще и понятнее, опытным пользователям эта статья не расскажет ничего нового, и некоторые приемы могут показаться неэффективными.
Для начала установим сам фреймворк SailsJS, изначально предполагается что у вас уже установлен пакет NodeJS, и есть доступ в интернет. В моем случае моя ОС — Fedora 20, с вашей стороны это может быть Mac OS X, Ubuntu и другие ОС, в примере мы будет использовать бета версию, для установки SailsJS глобально введите команду
sudo npm install -g sails@beta
После этого нам нужно создать новый проект — в Sails это делается просто и понятно.
sails new sails-blog --linker
cd sails-blog/
Поясню — параметр
new
озаглавливает что мы хотим создать новый проект, далее мы вводим название проекта, параметр
--linker
делает так, что в нашем проекте будут автоматически подключатся файлы для frontend-а: js, css, images и так далее, а также автоматически компилироваться файлы CoffeeScript и LESS что также очень удобно — но подробнее об этом позже. После этого мы переходим в директорию с сгенерированным проектом.
В Sails весьма удобно организована сторона отвечающая за размещение фронтенда, сервер транслирует все файлы которые располагаются в папке /assets которая находится в корне проекта. Сами же файлы из папки assets имеют примерно такой доступ, объясню на пальцах: предположим вы хотите разместить некую картинку image.png, вы размещаете ее в директории assets/images/ — в таком случае при работающем сервере этот файл будет доступен по адресу Хост/images/image.png. Это базовые сведения, а теперь установим bootstrap [6] скачиваем архив с исходниками [7] на LESS (люблю этот язык стилей).
Распакуйте папку less из архива в assets/styles/ — должно получится такое расположение папки assets/styles/less, далее переименуйте папку less в bootstrap (для удобства), далее после распаковки основной части bootstrap-а, мы должны подключить глифы, для этого скопируйте папку fonts из архива в корень assets (прим. /assets/fonts). Теперь откройте файл /assets/styles/importer.less в вашем любимом текстовом редакторе Sublime Text: этот файл по умолчанию подключен к основному шаблону и постоянно мониторится Grunt — автоматически компилируется в importer.css
соответственно потом испортируем bootstrap добавив в этот файл строку
@import 'bootstrap/bootstrap';
Чтобы подключить глифы в том-же importer.less нужно объявить переменную которая будет указывать путь к папке с глифами, т.к наши глифы располагаются в папке fonts — мы добавляем следующую строку в файл
@icon-font-path: '/fonts/';
Чтобы окончательно установить bootstrap нам осталось только закинуть файл jquery.js [8] и boostrap.js [9] в папку assets/js/dependencies/.
На этом мы закончим первоначальное знакомство с организацией фронтенда и статики в Sails и перейдем непосредственно к разработке самого Блога.
Для начала создадим комплекс API — состоящий из модели и контроллера, которые мы назовем post по вполне очевидным причинам, для создания комплекса API введем следующую команду:
sails generate api post
Сгенерированные файлы будут находится в одноименный папках в директории api/, Sails по умолчанию создает CRUD [10] API готовое к эксплутации, подробнее можно посмотреть на описывающем Sails видео [11].
Теперь откроем созданную нами ранее модель Post и начнем писать код, в модели нам нужно указать название атрибута, его тип, и валидатор. Сейчас приведу содержимое нашей модели.
module.exports = {
attributes: {
title: {
type: 'string',
maxLength: 120,
required: true
},
description: {
type: 'string',
required: true
},
content: {
type: 'string',
required: true
}
}
};
Составляющая нашей модели строиться очень похожей на JSON, что делает ее весьма понятной и удобной, как вы могли понять внутри конструкции atributes мы перечисляем атрибуты модели, в нашем случае нам нужно 3 атрибута — заголовок, короткое описание и контент. У всех 3 тип — строка, у заголовка установлено 2 валидатора: maxLength: максимальная длина строки, required: обязательный ли это атрибут при создании новой записи (в нашем случае обязательный), далее задаем параметры для оставшихся 2 атрибутов, для sails существуют десятки типов, и валидаторов на все случаи жизни (даже валидатор цвета HEX), посмотреть полный список вы можете здесь [12].
Итак, мы составили нашу первую модель которая будет отвечать за наши записи в БД — и манипуляции с ними. Теперь можем перейти к основным действиям с контроллером — api/controllers/PostController.js.
Манипуляции с контроллерами также происходят в удобном представлении JSON, для начала перечислим что мы хотим чтобы блог умел — и соответственно разделим задачи на элементы контроллера. Наш блог должен уметь отображать список постов с коротким описанием по убыванию (новые посты в начале, старые в конце), уметь разбивать список постов на страницы (пагинация) и страница на которой мы сможем увидеть полный контент отдельного поста с комментариями (disqus). Таким образом для себя я разделил эти возможности на 3 атрибута контроллера, и 3 основные функции манипуляции с записями, индексный: отображение последних 10 постов. просмотр: отображение полного контента конкретной запси.
пагинация — разбиение списка, и просмотр списка на определенном срезе списка. Начнем написание кода с функционала записи — добавление, обновление, удаление. Внутри module.exports — будем писать код.
create: function (req, res) {
var params = {
description : req.param('description'),
content : req.param('content'),
title : req.param('title'),
}
Post.create(params).exec(function (err, post) {
res.redirect('/post/watch/' + post.id);
if (err) return res.send(500);
});
}
В этом коде как вы поняли мы описываем создание новой записи в БД, как я уже говорил в Sails по-умолчанию встроено CRUD API, это значит что каждому подконтроллеру по url можно передать параметры с помощью GET или POST, а Sails уже сможет их обрабатывать.
Post.create — означает 1) что мы собираемся работать с моделью Post а метод create [13] отвечает за создание новой записи, в которую нужно передать список в котором мы указываем атрибут записи, и значение этого атрибута, в нашем случае запись должна генерироваться от передачи ей аргументов в котором мы и используем CRUD, в списке params я указываю в значении передаваемые параметры, если вам не понятно как это делается то объясню на пальцах, чтобы в нашем случае создать запись — мы отправляем POST запрос (например через Postman) с параметрами для title, description, content — на url /post/create принятые на этом url параметры могут вызываться с помощью req.param('параметр') что мы и сделали. 2) В методе exec мы используем анонимную функцию которая принимает в качестве аргументов err — возникшие в процессе создания ошибки, и post — данные с только что созданного нами поста, которые в дальнейшем мы обрабатываем таким образом чтобы если ошибка — он выдавал страницу 500, при успешном создании (когда мы получаем данные поста) мы перенаправляем на страницу с полным описанием (рассмотрим этот контроллер далее) передав в url идентификатор поста.
Следующим вспомогательным подконтроллером мы сделаем подконтроллер обновления данных, который очень удобен если нужно сделать редактирование информации.
update: function (req, res) {
var Id = req.param('id');
var elem = {
description : req.param('description'),
content : req.param('content'),
title : req.param('title')
};
Post.update(Id, elem).exec(function (err) {
if (err) return res.send(500);
res.redirect('/');
});
}
В этом случае метод update [14] очень похож на метод create — разница в том что первым аргументом мы указываем id записи — которую как и в прошлый раз мы получаем из переданного параметра. Суть этого кода как я думаю вы тоже уже уловили. Последней утилитой мы сделаем удаление записи.
delete: function (req, res) {
var Id = req.param('id');
Post.destroy(Id).exec(function (err) {
if (err) return res.send(500);
res.redirect('/post');
});
}
Для удаления [15] записи нам нужно только указать id.
Сейчас мы рассмотрим написание основной «лицевой» части нашего приложения, какие это части я описал выше, по традиции начнем с индексной страницы, многие могут сказать что в качестве индекса я мог бы просто назначить 1 страницу среза пагинации, но думаю новичкам будет лучше разжевать лишний раз. и так индекс.
index: function (req, res) {
Post.find()
.sort('id DESC')
.limit(5)
.exec(function (err, posts) {
if (err) return res.send(500);
res.view({
posts: posts
});
});
}
Теперь начну пояснения кода — метод find [16] отвечает за поиск записей в модели, в качестве аргументов к нему мы ничего не вызываем — значит под совпадение подходят все записи, после этого мы сортируем [17] записи в порядке убывания — в данном случае я использую id только в качестве примера, если вы собираетесь использовать такие БД как MySQL, Mongo и т.д то вы должны заменить id на createdAt по вполне очевидным причинам, и последней в нашем списке будет произведение первичного сечения списка постов с лимитом в 5 записей. После того как все процедуры с моделью закончены она возвращает нам список постов в нужном порядке, и количестве: так что дальше мы сможем использовать его в нашем представлении. Как вы помните из предыдущих манипуляций мы используем анонимную функцию в методе exec чтобы провести конечную обработку данных. Так что теперь перейдем к ключевой части — методу отображения, за это отвечает метод view в который мы передаем список того что будет доступно нам при составлении представления, в нашем случае это объектный список, для доступа я создаю элемент списка атрибута с названием posts и значением — возвращенным нам анонимной функцией атрибут posts.
watch: function (req, res) {
var Id = req.param('id');
Post.findOne(Id).exec(function (err, post) {
if (!post) return res.send(404);
if (err) return res.send(500);
res.view({
post: post
});
});
}
Здесь в качестве аргумента метода findOne [18] мы передаем аргумент идентификатора — который также является запросом, в ответ он выдает нам данные отдельного поста, к которым мы даем доступ из метода view.
Далее рассмотрим контроллер пагинации и настройки путей blueprints и перейдем непосредственно к составлению представления.
page: function (req, res) {
var page = req.param('page');
Post.find()
.sort('id DESC')
.paginate({
page : page,
limit: 5
})
.exec(function (err, posts) {
if (err) return res.send(500);
res.view({
posts: posts
});
});
}
Здесь мы делаем практически тоже что и в индексном контроллере, с той разницей что сюда мы добавили метод paginate [19], который принимает в качестве аргумента JSON в котором мы должны указать лимит записей на страницу, и саму страницу среза которую мы хотим отобразить. Чтобы сделать страницу среза более динамичной — мы создаем переменную page — с запросом который будет задавать страницу — для большего удобства мы сделаем передачу этого аргумента как get запрос — без лишних элементов запроса: напрямую, в конфигурации путей [20]. Для этого откроем файл config/routes.js и начнем правку. Добавьте в module.exports.routes следующее:
'get /post/:page': {
controller: 'post', // Контроллер
action: 'page' // Действие
},
Что тут делается? в принципе все предельно просто мы назначаем путь — вид запроса сначала, url, и передаваемый атрибут - :page — который мы использовали в нашем контроллере (req.param('page')) и упростили вариант его передачи в контроллер (думаю это лучше - /post/page?page=2). Заодно с пагинацией зададим упрощенную схему управления для наших утилитных функций:
'post /post/create': {
controller: 'post',
action: 'create'
},
'get /post/delete/:id': {
controller: 'post',
action: 'delete'
},
'post /post/update': {
controller: 'post',
action: 'update'
}
Итак, мы произвели основные манипуляции с контроллером Post и теперь для окончательного вступления в силу нам осталось только написать представление — которое будет лицом приложения. Если кого затруднило составление кода — вот полный вариант контроллера Post [21] с комментариями.
Представления в Sails строятся автоматически по контроллерам, мы создали контроллер Post — значит папка с представлениями этого контроллера будет находится по адресу views/post/*, а представления имеют имена подконтроллеров в которых есть метод views, Sails поддерживает множество шаблонизаторов включая Jade, Handlebars, HAML и другие но по умолчанию в него встроен EJS так что наши представления будут строится на нем. Создадим папку post в views, и добавим туда файл index.ejs и page.ejs c следующим содержанием:
<div class="container text-center">
<h2 class="text-center">MY BLOG APP</h2>
<div class="row">
<div class="col-md-1"></div>
<div class="col-md-10">
<% _.each(posts, function (post) { %>
<div class="panel panel-default">
<div class="panel-body">
<h3 class="text-center"><%= post.title %></h3><hr>
<article>
<%= post.description %>
</article>
</div>
<div class="panel-footer">
<a href="/post/watch/<%= post.id %>" class="btn btn-info">LEARN MORE</a>
</div>
</div>
<% }) %>
</div>
<div class="col-md-1">
</div>
<ul class="pagination">
<li><a href="/post">1</a></li>
<li><a href="/post/2">2</a></li>
<li><a href="/post/3">3</a></li>
</ul>
</div>
_.each() в качестве первого параметра — массив значений, следующий callback который выдает нам данные от единичного элемента массива (чем-то похоже на ng-repeat из angular), далее мы конструируем данные из который должен строиться повтор значений, думаю те кто знакомы с EJS понимают что значения переменных мы заключаем в <%= %> т.к это текст, или в <% %> заключаем функции (если совсем просто объяснять). Так что думаю основной поток информации о EJS вам понятен хотя-бы на интуитивном уровне — если нет то документация [22] в помощь. И последнее представление — это единичное отображение конкретной записи — views/post/watch.ejs
<div class="container">
<div class="panel panel-default">
<div class="panel-body text-center">
<h3><%= post.title %></h3><hr>
<article>
<%= post.content %>
</article>
</div>
</div>
</div>
На этом основная часть функционала нашего блога создана — он умеет создавать, редактировать и удалять посты, в него включена пагинация, и просмотр отдельных записей, да пока у нас нет формы в админке которая могла бы создавать записи визуально — но для начала можно протестировать используя Postman [23], предварительно запустив тестовый сервер командой
sails lift
Надеюсь материал из первой части был интересен и полезен, во второй части будет описываться создание сессий, авторизации, и написание простой админки, если вы не хотите ждать, или неправильно поняли как должен быть написан код то можете посмотреть полный код проекта на github [24] с полными комментариями, описывающими все что там делается.
Полезные ссылки и материалы используемые при написании:
Автор: friktor
Источник [28]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/news/60732
Ссылки в тексте:
[1] SailsJS: http://sailsjs.org/#!
[2] NodeJS: http://ru.wikipedia.org/wiki/Nodejs
[3] MVC: http://ru.wikipedia.org/wiki/Model-View-Controller
[4] документацию: http://sailsjs.org/#!documentation
[5] посмотреть касты: http://irlnathan.github.io/sailscasts/blog/archives/
[6] bootstrap: http://getbootstrap.com
[7] исходниками: https://github.com/twbs/bootstrap/archive/v3.1.1.zip
[8] jquery.js: http://code.jquery.com/jquery-2.1.1.min.js
[9] boostrap.js: http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js
[10] CRUD: http://ru.wikipedia.org/wiki/CRUD
[11] видео: http://www.youtube.com/watch?v=GK-tFvpIR7c
[12] здесь: http://sailsjs.org/#!documentation/models
[13] метод create: https://github.com/balderdashy/waterline-docs/blob/master/query-methods.md#create-criteria-callback-
[14] метод update: https://github.com/balderdashy/waterline-docs/blob/master/query-methods.md#update-search-criteria--values--callback-
[15] удаления: https://github.com/balderdashy/waterline-docs/blob/master/query-methods.md#destroy-criteria--callback-
[16] find: https://github.com/balderdashy/waterline-docs/blob/master/query-methods.md#find-criteria-callback-
[17] сортируем: https://github.com/balderdashy/waterline-docs/blob/master/query-language.md#sort
[18] findOne: https://github.com/balderdashy/waterline-docs/blob/master/query-methods.md#findone-criteria-callback-
[19] paginate: https://github.com/balderdashy/waterline#pagination
[20] конфигурации путей: http://sailsjs.org/#!documentation/routes
[21] полный вариант контроллера Post: https://github.com/friktor/sails-blog/blob/master/api/controllers/PostController.js
[22] документация: http://embeddedjs.com/
[23] Postman: https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm
[24] код проекта на github: https://github.com/friktor/sails-blog
[25] Базовая документация Waterline: https://github.com/balderdashy/waterline
[26] Подробная документация о запросах Waterline: https://github.com/balderdashy/waterline-docs/blob/master/query-language.md
[27] Методы запросов Waterline: https://github.com/balderdashy/waterline-docs/blob/master/query-methods.md
[28] Источник: http://habrahabr.ru/post/224195/
Нажмите здесь для печати.