пятница, 16 октября 2015 г.

Перевод проекта ChWiSe.Net на Backbone.js

После относительно длительного перерыва я продолжил не спеша развивать свой проект "химического поиска" ChWiSe.Net. В какой-то момент я обнаружил, что браузерный Javascript-код начинает быть запутанным и добавление новой функциональности стало нелёгкой задачей. Я решил, что пора провести мини-рефакторинг этой части. При этом возникла мысль использовать какой-либо Javascript-фреймворк. Сейчас существуюет большое количество Javascript-фреймворков, призванных облегчить и струкутрироровать клиентский код в AJAX-приложениях. При этом когда встаёт проблема выбора, если ты не знаком с парой-тройкой фреймворков, фразы типа "у каждого фреймворка своя ниша" вызывают чувство беспомощности. Совершенно непонятно, конкретно мой проект в какой нише, и какой фреймворк подойдёт лично мне лучше всего. В моём конкретно случае я остановился на Backbone.js, и, кажется, не ошибся. В этом посте я собираюсь написать о своём опыте перевода браузерной части несложного проекта с "просто javascript" на фреймворк Backbone.js. Сам по себе этот пост вряд ли годится в качестве хорошего туториала по Backbone.js (хотя я постарался дать ссылки на такие туториалы), однако тут разбира один частный случай веб-сайта, в котором Backbone.js работает хорошо.

 Одна из ключевых деталей, которых не хватало в имевшейся реализации - url hash-навигация (или просто hash навигация). Url hash навигация это такой приём в AJAX-приложении, когда ваши действия на веб-страничке, такие как поиск, центрирование карты, выбор объекта или, скажем, фильтра по колонке какой-нибудь таблицы фиксируются адресной строке браузера после символа '#'. Вы можете выполнтить какие-то действия, например поискать что-то в вашем любимом поисковике, затем скопировать URL страницы с результатами поиска и отправить ссылку вашему знакомому. Он в свою очередь, перейдёт по этой ссылке и увидит примерно то же что видели и вы: например, на страничке сайта поисковика в поисковом поле будет тот же самый запрос, а результаты будут примерно теми же (жизнь как всегда сложнее, современные поисковые движки могут давать персонализированные результаты, поэтому пользователь Б на один и тот же запрос может увидеть другие результаты, чем пользоваетль А, но сам запром будет тем же самым). Ещё одно немаловажное преймущество страничек с url hash-навигацией - адекватная работа кнопки "назад" в браузерах. Поскольку мой сайт тоже по сути дела "поисковик", для меня это фишка была обязательна.

Между делом я почитывал статьи про современные JS-фреймворки, и больше всего заинтересовался Backbone.js. Во-первых он поддерживает url hash навигацию. Хотя для реализации url hash навигации можно обойтись и без фреймворков, Backbone.js предлагает для этого логичную и легко поддерживаемую структуру кода. Во-вторых, как мне показалось, моя веб-страничка по структуре идеально вписывается в "типовую архитектуру" Backbone.js приложения (об этом ниже). Ну и, наконец, Backbone.js очень хвалили за "легковесность" и отсутствие "магии": исходный код Backbone.js относительно невелик и, как сообщают, его несложно прочесть и понять. Надо учитывать, что Backbone.js использует jQuery и Underscore.js. Поэтому при добавлении в ваш проект зависимость от Backbone.js их размер тоже стоит учесть. Если вы и так использовали эти библиотеки, то объём данных, скачиваемых браузером при заходе на вашу страницу увеличится только на размер самого скачиваемого файла Backbone.js.

В моём конкретном случае клиентская часть уже и так использовала jQuery, так что мне пришлось притянуть в проект лишь библиотеку Underscore.js. Зато я смог отказаться от шаблонной библиотеки EJS, так как в Underscore.js уже есть движок шаблонов.

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

Именно для таких страничек Backbone.js подходит очень хорошо! В общем, я решил переделать клиентскую часть с использованием Backbone.js.

Для создания таких сайтов Backbone предлагает такие абстракции:

"Model" (модель) - обычно это внутреннее представление информации о некоторой еденичной сущности: цена и характеристики товара, координаты и тип объекта на карте, "поля" некой карточи в картотеке - всё это хорошие кандидаты на то, чтобы быть смоделированными моделью Backbone.js.

"Collection" - по сути это тоже модель, которая содержит в себе другие модели. Удобно думать о коллекции как о простом массиве. Обычно Collection соответствует различным спискам, а отдельные элементы в модели-массиве - отдельным записям в этих списках. При этом модели совсем необязательно быть элементом коллекции. Например, у меня в ChWiSe.Net есть два класс моделей "нижнего уровня". Одна из них, ChWiSe.Models.SearchResultCompound отвечает за внутреннее представление одного результата поиска. Другая, ChWiSe.Models.ServerMessage отвечает за представление "сообщения от сервера", такие как ошибки, исправлнеие опечаток и.т.д. Так же есть модель-коллекция, которая содержит все результаты поиска ChWiSe.Models.SearchResults. Элементы этой коллекции - модели нижнего уровня типа ChWiSe.Models.SearchResultCompound . Сообщения от сервера класса ChWiSe.Models.SearchResultCompound  так же содержаться в этой модели-коллекции просто в виде дополнительного поля, а не элемента коллекции.

Модели Backbone.js обеспечивают не только представление ваших данных в клиентской части веб-приложения, но и взаимодействие с сервером.  Можно вносить свои коррективы в этот процесс переопределяя в ваших моделях функции url, fetch и parse.

"View" (вид) - отвечает за "рендеринг" модели. Под рендерингом подразумевается генерация html-разметки, которая будет отображаться в браузере. Я уже упомянул, что мне понадобилось 3 различных модели. Я сделал столько же представлений: ChWiSe.Views.SearchResultCompound, ChWiSe.Views.ServerMessage и ChWiSe.Views.SearchResults. Проилюстрирую роль моделей и представлений в моём проекте следующими рисунками:






"Router" (маршрутизатор). Именно этот компонент отвечает за hash-навигацию. В объекте-маршрутизаторе задаётся соответствеие шаблона hash-строки в адресной строке браузера и javascript-функции. Наприемер, у меня таблица маршрутизации очень простая и пока что выглядит так:

routes: { // sets the routes
    "" : "showWelcomePage",
    "search?*queryString" : "search",
  },

Смысл очень простой: напримре, если в строке браузера появится URL, заканчиваюзийся на search?sq=ethanol, то вызовется JS-функция search. При этом в качестве аргумента её передастся строка "sq=ethanol". В скрипте (например, в обработчике нажатия на кнопку "Search") мы не вызываем функцию, отвечающую за поиск непосредственно, а "переходим" на соотвтетствующий URL:

ChWiSe.router.navigate("search?" + encodedURIfragment, {trigger: true} );

Вот и всё!

В качестве ещё одного дополнительного украшения я сделал бесконечную прокрутку результатов (похоже на то, как сделано в Facebook, Вконтакте и DuckDuckGo) вместо щёлкания по кнопкам-ссылкам с номерами страниц. Тут мне помогла библиотека infiniScroll.js.

Заключение:

В целом Backbone.js мне понравился: большое сообщество, хорошие туториалы, относительная простота в освоении - всё это несомненные преимущества этого фреймворка. Ещё один плюс - типовая архитектура клиентского кода. Так что побочным положительным эффектом от перехода на Backbone может стать лучшая структурированность JavaScrip-кода вашего проекта.

Ссылки:

Неплохая статья для начала чтобы пощупать Backbone.js

Пожалуй наиболее всеобъемлющее введение в Backbone.ks. Рекомендую для продолжения знакомства с Backbone.js сразу послед прочтения предыдущей статьи

Конечно же, документация на официальном сайте