Персональный блог Игоря Антонова aka "spider_net"

TODO. Простейший список задач с помощью sails.js и angular.js

TODO. Простейший список задач с помощью sails.js и angular.js

Честно тебе скажу: в нашем журнале действует самая настоящая тайная ложа фанатов JavaScript. Главный редактор, выпускающий редактор, ваш покорный слуга — при виде очередного интересного JS-фреймворка мы сразу впадаем в веселое возбуждение и начинаем писать про него статью. Сломив встречное сопротивление наших хардкорщиков (это легко сделать, их здоровье подточено кодингом на сях и ночами за дизассемблером), мы выкатываем на твой суд очередной полезный материал. Прошу любить и жаловать — Sails.js, который способен порадовать даже матерых фанатов Node.js тем, что разрабатывать с помощью его на сервере можно совершенно без геморроя.

Node.js — это очень круто, но изобилие мощных фреймворков, упрощающих разработку веб-приложений для других языков программирования, существенно тормозило популярность применения JavaScript на севере. Многим нужен был простой инструмент в стиле любимых RoR, Yii или ASP .NET MVC. Сообщество любителей JavaScript усиленно работало над исправлением этой досадной ситуации, в результате чего появилось несколько интересных решений. У каждого из них в арсенале есть свои киллер-фичи, но из всего этого многообразия мое внимание больше привлек многообещающий проект, скрывающийся под скромным названием Sails.js.

Муки выбора

До Sails.js на моем операционном столе побывали Derby, Meteor, маленький Rendr, незамороченный Geddy и даже экзотичный Tower. Со всеми этими вещами мне пришлось провозиться несколько дней, и самым крутым из них мне показался Derby. Я уже было хотел на нем и остановиться, но отсутствие нормальной документации и потребность во вдумчивом кодокопании для исправления простейших ошибок вынудили меня продолжить поиски.

Не успел я попрощаться с Derby, как мне на глаза попался совсем юный, но уже с внушительным числом звезд на GitHub Sails.js. Тогда проект находился практически в зачаточном состоянии, но стремление разработчиков соответствовать идеологии великого и ужасного RoR мне очень понравилось, и я решил познакомиться с ним поглубже. Сразу отмечу, что за время написания этой статьи (а пишу я долго и всегда задерживаю. — Прим. ред. от имени И. Антонова) Sails.js довольно сильно менялся, но разработчики всегда уделяют достаточно времени написанию документации, поэтому миграция на очередную версию проблем не вызывает.

Крутые фичи Sails.js

Первым делом мне хочется отметить, что Sails.js — это в первую очередь MVC-фреймворк. При наличии опыта работы с любым другим популярным фреймворком (RoR, Yii, CodeIgniter, ASP .NET MVC) сразу после ознакомления со структурой проекта Sails.js ты почувствуешь себя комфортно, ведь в глаза тебе бросятся знакомые по паттерну MVC папочки: models, views, controllers и другие характерные для подобных продуктов вещички.

Несмотря на свою идеологическую и архитектурную похожесть на RoR, Sails.js может похвастаться низким порогом вхождения. После того же Derby мне показалось, что я пересел с ручной коробки на современный автомат.

Что касается киллер-фич, то их тут действительно много. Например, полная интеграция с socket.io. Это значит, что Sails.js-разработчики без проблем могут разрабатывать приложения реального времени! Им не нужно будет мучиться с интеграцией или налаживанием корректной работы с Node.js, все работает вполне корректно из коробки.

Особого внимания также заслуживают консольные утилиты, тотально автоматизирующие рутинные операции. Хочется создать новый контроллер? Нет проблем! Пишем одну команду, и заготовка для контроллера готова. Что? Вместе с контроллером требуется модель? Опять без проблем, для этого тоже есть команда. Чуть позже мы в этом убедимся на практике.

При работе с Sails.js мне особенно понравилась его дружественность к другим фреймворкам/библиотекам. Этот фреймворк не пытается решить все возможные проблемы самостоятельно, а делегирует непрофильные для себя задачи вспомогательным решениям. Таким образом, задача сдружить Sails.js со своим любимым клиентским фреймворком превращается в пустяковое дело.

Вместе с Sails.js поставляется достаточно мощная ORM — Waterline. Она быстра, проста в использовании и в состоянии подружиться с любой базой данных. Сразу из коробки нам предлагают адаптеры для наиболее популярных в области СУБД решений: MySQL, PostgreSQL, Redis, MongoDB, а также дают возможность использовать обычный файл для хранения данных. Таким хранилищем удобно пользоваться на этапе разработки, и сегодня мы увидим это на реальном примере.

Самую главную фишку Sails.js я припас напоследок. Только представь, что всю рутину, связанную с генерацией RESTful API Sails.js неплохо автоматизирует. На практике это означает, что для созданной модели фреймворк автоматом умеет создавать необходимый API для типичных операций (CRUD, пагинация, поиск) с настроенными маршрутами. Вот так просто создаем модель, и сразу можно с ней работать без единой строчки кода. По секрету скажу, именно эта функция вдохновила меня на более плотное знакомство с Sails.js.

Официальный сайт проекта Sails.js

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

Расправляем паруса

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

Наш будущий список задач будет создан на популярном стеке технологий. На backend’е будет властвовать Sails.js, а front-end отдадим на растерзание моему любимому AngularJS. Ну и чтобы жизнь совсем не казалась сахаром, обеспечим загрузку необходимых библиотек на клиенте с помощью RequireJS. Усаживайся удобнее и запасись колесами от морской болезни. Мы отправляемся в плавание!

Подготавливаем окружение

Для выполнения поставленной цели нам потребуется ряд инструментов. В первую очередь это Node.js, NPM и Sails.js. Лучше всего поднимать эти компоненты в их родной среде, то есть unix-like ОС (Linux, OS X, BSD). В Windows эту связку настроить теоретически возможно, но в последнее время лично я при этом сталкивался со странными ошибками.

Если у тебя нет установленного Linux-дистрибутива, то не парься с виртуалками, а сразу воспользуйся услугами облачного хостинга DigitalOcean. В пресетах DO имеется заготовка с последней версией Ubuntu и настроенным Node.js, поэтому поднятие всего необходимого рабочего окружения для тебя сведется к нескольким кликам мышкой. Для себя я выбрал именно этот вариант.

В общем, определяйся и после подготовки рабочего окружения устанавливай Sails.js с помощью команды sudo npm -g install sails.

Создаем новый проект

Перед тем как отдать команду для формирования нового проекта, хочу сразу вооружить тебя ссылкой на репозиторий с законченным проектом. Ссылка ведет на мой аккаунт в BitBucket, откуда ты без проблем сможешь клонировать законченный проект. Рекомендую сразу же это сделать, так как некоторые листинги кода достаточно объемные и полностью в статью не поместятся.

С организационными моментами разобрались, теперь отдаем команду на создание скелета нового проекта:

sails new todo

Вот таким простым образом мы подготовили болванку для нашего приложения. Сразу переходи в директорию проекта и бегло осмотри его структуру.

В сотый раз говорить о том, что контроллеры и другие сущности MVC должны лежать в одноименных папках, не буду ;), лишь обращу твое внимание на директорию assets. В нее следует помещать все ресурсы, которые должны быть доступны клиенту. Под ресурсами подразумеваю CSS/HTML/JS/картинки и прочее.

На данном этапе наш проект готов к запуску, поэтому вбивай команду sails lift и заходи с браузера на свой хост (по умолчанию 1337). Если все компоненты установились правильно, то ты увидишь стандартную страницу приветствия.

Запуск проекта в терминале

Новый проект запущен

Настраиваем RequireJS

Все дополнительные инструменты мы будем подтягивать с популярных CDN, а не хранить локально — в реальном проекте это приведет к уменьшению времени загрузки страницы. А все потому, что CDN’ы используют многие популярные проекты, в связи с чем в кеше у многих пользователей с высокой степенью вероятности уже будет валяться тот же Angular или Require. Следовательно, повторно загружать большой файл с библиотекой ему не придется.

Теперь определимся с предназначением RequireJS. Если не вдаваться в технические подробности, то с помощью этой крохотной либы легко можно навести порядок в том библиотечном бардаке, которым обрастает любой современный проект. RJS приносит нам модульность и обеспечивает асинхронную загрузку файлов. Плюсы очевидны: наше приложение загружается быстрее и мы точно знаем место подключения всех сценариев.

OK, теперь определимся с местом подключения библиотеки RJS. Она должна подключаться ко всем страницам, где будут использоваться сторонние сценарии. В рамках нашего примера таким местом должна быть мастер-страница. Sails.js определяет такую сущность как Layout (слой). В слое (аналог мастер-страницы из мира ASP .NET MVC) мы можем определить базовый шаблон страницы, а изменяемую часть подсовывать из конкретного представления.

Sails.js не навязывает использование какого-нибудь определенного шаблонизатора, но в текущей версии работоспособность слоев поддерживается только в стандартном EJS. Он не самый мощный, но для примера и большинства распространенных задач его хватит за глаза.

Открывай файл views/layout.ejs, удали все его содержимое и напиши в него базовую верстку страницы из листинга 1. Обрати внимание на переменные, заключенные в <%-%>. Их значение мы будем передавать из контроллеров. В рамках листинга таких вызовов два — title и body.

После закрывающего тега body подключаем библиотеку require.js из популярного CDN CloudFlare. Следует обратить внимание на атрибут data-main в теге script. В нем указан путь к конфигурационному файлу (main.js), который нам предстоит создать.

Поскольку мы делаем одновременно и AngularJS-приложение, то определим его начало в теге body. Для этого используем директиву ng-app. Подробно на директивах AngularJS останавливаться не будем, поскольку об этом мы (более того, лично я :)) уже писали в «Хакере» в июльском номере за прошлый год.

Листинг 1. Готовим layout.ejs

<!doctype html>
        <html>
                <head>
                        <meta charset="utf-8">
                         <title><%- title %></title>
                        <link rel="stylesheet" href="/styles/style.css">
                </head>
                <body ng-app="todoapp">
                  <%- body %>
                </body>
        <script src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.11/require.min.js" 
		data-main="/js/main.js">
        </html>

Конфигурируем RequireJS

Создадим конфигурационный файл main.js в assets/js. В нем мы должны определить библиотеки, которые требуется подключить к проекту, и заодно позаботиться об инициализации AngularJS. Код конфигурационного файла приведен в листинге 2.

Листинг 2. Конфигурируем RequireJS

window.name = 'NG_DEFER_BOOTSTRAP!';
        require.config({
          'baseUrl': '/js',
          'paths': {
            'angular': '//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular'
          },
          'shim': {
            'angular': {
              'exports': 'angular'
            }
          }
        });
        require([
          'angular',
          'app'
        ], function (angular, app) {
          angular.element(document.getElementsByTagName('html')[0]);
          angular.element().ready(function() {
            angular.resumeBootstrap([app.name]);
          });
        });

Поскольку модули мы будем брать из CDN, то путь к ним необходимо прописать в свойстве path. Секция shim определяет название модулей (читай — библиотек), которые созданы не в AMD (не путать с производителем процессоров) стиле. Далее выполняется ручная инициализация AngularJS, подробнее об этом процессе — в соответствующей статье.

Готовим героя

Загрузчик модулей настроен, теперь пришла очередь Angular. Тут особо настраивать нечего, достаточно определить зависимости, зарегистрировать приложение в виде модуля (то есть в пространстве angular.module) и описать с помощью директивы require модуль, который будет загружать RequireJS.

Создадим файл controllers.js в assets/js и определим в нем новый RJS-модуль с кодом из листинга 3 (кстати, если кого раздражает слово «листинг», пишите об этом напрямую Игорю, я ничего не могу с ним сделать :). — Прим. ред.). В рамках этого модуля мы подтягиваем сам Angular и регистрируем контроллеры в глобальном пространстве angular.module. После этого все объявленные контроллеры будут доступны во время выполнения нашего приложения.

Далее по коду выполняется регистрация нового контроллера TodoCtrl, который должен располагаться в директории assets/js/controllers. Этот контроллер будет содержать всю необходимую логику для работы нашего списка задач. При необходимости создания нового контроллера нам просто потребуется добавить в этот файл одну-единственную строчку. Удобно!

Листинг 3. Регистрируем контроллеры Angular

define(function(require){
            var angular = require('angular'),
                Controllers = angular.module('controllers', []);
            Controllers.controller('TodoCtrl', require('controllers/TodoCtrl'));
            return Controllers;
        })

Финальным шагом подключения AngularJS будет определение RJS-модуля (файл app.js в assets/js), который зарегистрирует приложение todoapp и подтянет в него наши контроллеры. Код приведен в листинге 4.

Листинг 4. Регистрируем todoapp

define([
            'angular',
            'controllers'
        ], function (angular) {
            app = angular.module('todoapp', ['controllers']);
            return app;
        });

Серверные контроллеры

Подготовительные работы на клиенте завершены, теперь немного серверной магии. Договоримся, что наше приложение будет состоять из двух страниц. Первая будет носить чисто информационный характер (на нее юзер будет попадать при обращении к полному имени хоста), а вторая — содержать форму списка задач. Для реализации вышесказанного придется создать дополнительный контроллер: sails generate controller welcome.

Команда создаст заготовку будущего контроллера с именем WelcomeController.js в api/controllers. Откроем этот файл в редакторе и допишем в module.exports:

index: function(req, res) {
                res.view('welcome', {
                    title: "Добро пожаловать!"
                });
            }

В терминологии Sails.js здесь мы определяем новое действие (index). Результатом его выполнения будет рендеринг представления с именем welcome (см. метод view). Вторым параметром методу view мы передаем объект, свойства передаются в представления. У нас оно одно — title, его значение будет выведено в вызове шаблонного тега.

Работа идет полным ходом

Чтобы управление передавалось сразу именно этому контроллеру (то есть во время обращения пользователя к корню), необходимо внести изменение в роутинг. Открываем файл routing.js из папки config и заменяем маршрут «по умолчанию» на

'/': {
            controller: 'WelcomeController',
            action: 'index'
          }

Здесь мы определили контроллер для маршрута "/", а также метод действия. Остается создать представление welcome.ejs в views и запустить проект для промежуточного тестирования. В представлении я просто написал одну строку приветствия и опубликовал ссылку на контроллер, отвечающий за вывод представления с формой списка задач. Проделай аналогичные действия и протестируй пример в действии (sails lift).

Контроллер Welcome в работе

Модели, контроллеры и API

Медленно, но верно мы приближаемся к финалу морского приключения, и теперь настало самое время потрогать генератор API. Для описания сущности «задача» нам потребуется отдельная модель task и одноименный контроллер. Сгенерировать все это добро одним махом поможет команда: sails generate api task. На выходе мы получим сразу модель, контроллер и анонсированный API для CRUD-операций.

Перед тестом API опишем модель. Всего в ней будет два поля: title (string) и completed (boolean). Нетрудно догадаться, что в первом мы будем сохранять название задачи, а во втором ее состояние (выполнена или нет). Описание модели приведено в пятом листинге. При описании модели можно использовать различные валидаторы. В поставку входят валидаторы для проверки типичных значений (email, url, post code и так далее).

Листинг 5. Модель Task

attributes: {
           "title": {
                "type": "string",
                "required": true
            },
            "completed": {
               "type": "boolean",
               "defaultsTo": false
             }

На этом вопрос с моделью закрыт, переходим к тестированию API. Запускаем проект и в адресную строку браузера вбиваем запросы:

your_host:1337/task/create?title=Проверка&completed=false
        your_host:1337/task/create?title=еще одна задача&completed=false

Результатом выполнения каждого из них будет представление переданных данных в JSON-формате. Стоит сразу обратить внимание, что сервер не просто их возвращает в удобном виде, а сразу же сохраняет в хранилище. Поскольку мы не подключали адаптер для работы с БД, в роли хранилища выступает обычный файл. Для проверки сохранения данных выполни еще один запрос, результатом будут добавленные нами записи:

your_host:1337/task/find

Тестируем API

Результат сохранения данных

Последние штрихи

Серверная часть нашего приложения полностью готова, остается только создать представление с отображением формы списка задач, а также написать немного клиентского кода для взаимодействия с серверным API. Начнем с представления. Код разметки достаточно компактный, но в журнальной статье ему места все равно не хватит. Создай самостоятельно файл todoList.ejs в директории views и перенеси в него код из репозитория.

Весь этот код был продемонстрирован в прошлогодней статье про Angular (есть на нашем сайте). Здесь лишь дам краткие пояснения. Все задачи у нас помещаются в массив todos. На клиенте нам остается только все их вывести при помощи стандартных директив и добавить директивы, отвечающие за взаимодействие c пользователем (добавление, удаление, пометка выполнения).

Сама клиентская логика описана в файле assets/js/controllers/TodoCtrl.js. Небольшой фрагмент кода этого файла приведен в листинге 6. Итак, в области видимости объявляется пустая коллекция. Далее с помощью сервиса $http выполняется запрос к сформированному ранее API. При старте приложения нам надо получить все данные, поэтому запрос делаем к find.

Запрос должен вернуть коллекцию элементов в формате JSON. Результатом выполнения метода Get будет примесь, которую необходимо проиндексировать. Запускаем цикл и в нем выполняем индексацию. Проиндексированную коллекцию помещаем в $scope.

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

Остальные операции (удаление, редактирование) выполняются аналогичным образом, меняется только имя используемого метода сервиса $http. Например, редактирование данных реализуется с помощью put, а удаление — delete. В реальном приложении стоит еще предусмотреть обработку ошибок, но это уже отдельная история.

Листинг 6. Фрагмент контроллера TodoCtrl

$scope.todos = [];
                $http.get('/task/find').success(function(data) {
                    for (var i = 0; i < data.length; i++) {
                        data[i].index = i;
                    }
                    $scope.todos = data;
                });
                // Добавление новой задачи
                $scope.addTodo = function() {
                    if (!$scope.newTodo.length) {
                        return;
                    }
                $http.get('/task/create?title=' + $scope.newTodo).success(function(data){
	
                        $scope.todos.push({
                            title: $scope.newTodo,
                            completed: false
                        });
                        $scope.newTodo = '';
                    });
                };

Тестируем проект

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

Законченный проект

Спускаем паруса

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

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

Что почитать по теме

Пока по Sails.js (особенно по актуальной его версии) в Сети не так много информации. Напомню, разработчики внесли ряд значительных изменений в последний релиз, который серьезно повлиял на обратную совместимость. Приведенные ниже ссылки могут стать отправной точкой для поиска адекватной информации.

  • http://goo.gl/YZaSX5 — к моменту выхода этой статьи в печать в моем блоге будет опубликован цикл заметок о Sails.js. В них будет подробно рассматриваться процесс разработки полноценного приложения с использованием Sails.js.
  • http://goo.gl/iQq7Ik — пример разработки с использованием стека Sails.js + Reactive.js + Backbone.js. Автор решает простую задачу — создает приложение для оформления счетов и прекрасно демонстрирует в работе связку актуальных веб-технологий.
  • http://goo.gl/AlKm99 — в статье рассматривается интеграция Passport в Sails.js. В примере используется устаревшая версия Sails.js, поэтому придется внести несколько корректив.
  • http://goo.gl/D9Zv4n — создание блога на Sails.js.
  • http://goo.gl/BttFnr — большая подборка видеороликов, автор которых рассматривает процесс создания полноценного сайта с использованием Sails.js. Минус у роликов один — английский язык.
  • http://goo.gl/2PZh2r — документация по ORM Waterline.
  • https://bitbucket.org/iantonov/todo/ — репозиторий с полноценным примером к статье.
  • http://goo.gl/tLpBOZ — моя большая статья про AngularJS.
  • Любителям кофе

    К CoffeeScript разработчики относятся по-разному: одни не представляют без него процесс разработки, другие плюются и всячески ругают. Я сохраняю нейтралитет и чаще всего обхожусь без помощи синтаксического сахара. Если ты не разделяешь моего мнения и уже привык к CS, то можешь продолжать его использовать в Sails.js. Для этого необходимо выполнить два простых действия:

    Установить пакет:

    coffee-script: npm install coffee-script --save

    В конфигурационный файл проекта (app.js) добавить строку: require('coffee-script/register');

    Использовать для генерации API (контроллеров, моделей) аргумент --coffee. Например, sails generate api mymodel - coffee .

    Статья опубликована в журнале "Хакер" (http://xakep.ru). Январь 2015 г.

    Ссылка на журнал: http://goo.gl/JdOLUJ

    Исходники примера TODO. Простейший список задач

    Оставьте комментарий!
    comments powered by HyperComments