Cells.js – еще один подход к разработке современных веб-приложений
Алексей Кондратенко, Altoros Development
Membase NoSQL database management system has administrative interface implemented as modern ‘single-page’ aka ‘pure-js’ web application. This talk will describe Cells.js — a library that was grown inside this user interface component. It facilitates structuring of user interface as a collection of inter-dependent variables or cells. This provides some otherwise hard to implement features, like back button support, for free. And I believe, that this leads to cleaner and smaller code base.
В последние годы получила распостранение такая практика организации веб-приложений, когда на стороне браузера реализуется вся логика пользователького интерфейса. Серверная сторона при этом предоставляет только «голый» API для доступа к данным. Преимуществом такого подхода является более отзывчивый интерфейс, т.к. реакция на действия пользователя реализуется на стороне браузера.
Одной из проблем построения пользователького интерфейса в таком виде является относительная неадаптированность современных web-стандартов к такого рода приложениям. Эти стандарты создавались в основном для статического web. Другой известной проблемой является неполная или неправильная реализация стандартов в разных браузерах.
Для реализации несложной логики зачастую достаточно применить одну из библиотек для кросс-браузерного манипулирования DOM и кросс-браузерной реализации XMLHttpRequest. В последнее время в этой нише доминирует библиотека jquery. Реализация более сложной логики быстро превращает код в запутанную и трудно поддерживаемую кашу из обработчиков событий и требует перехода к какому-либо способу организации более богатого пользователького интерфейса.
К одному из таких способов относятся MVC и его производные. На сегодняшний день имеется масса тяжеловесных фреймворков для реализации MVC, например Google Web Toolkit, ExtJS, SproutCore.
Однако, на мой взгляд, в средних по размеру приложениях применение более компактных и простых решений оправдано.
При создании web-интерфейса для Membase было изначально решено использовать только jquery. Однако после реализации первых экранов я понял, что без более продвинутой организации логики этот код будет невозможно поддерживать и развивать.
Большинство MVC-фреймворков имеет в своем основании систему наблюдения за данными и реакции на их изменения. Это позволяет отделять логику получения/обработки данных от логики обработки пользователькой реакции и логики представления этих данных пользователю.
Я начал с создания простого класса, реализующего наблюдаемое значение и возможность «подписаться» на его измeнения. Это помогло структурировать код лучше. Но затем я заметил, что некоторые значения полностью детерминированно зависят от других значений. Я вспомнил про проект cells, реализованный на Common Lisp (http://common-lisp.net/project/cells/) и понял, что могу легко реализовать основную идею этого проекта на javascript.
В результате пользователький интерфейс Membase (и до этого Northscale Memcached Server) организован как набор связаных зависимостями по данным ячеек. Большинство ячеек является вычисляемыми, т.е. каждой из них соответствует детерминированная функция на javascript. Значения функций вычисляемых ячеек могут зависеть только от значений других ячеек. И библиотека организует (пере)вычисления ячеек когда значения их зависимостей либо становится известно, либо изменяется.
Когда одной из исходных ячеек присваивается какое-либо значение, например, в результате действия пользователя, cells.js организует перевычисление всех ячеек, которые зависят от этой ячейки. После чего перевычисляются ячейки, зависимые от только что перевычисленных ячеек, и так далее. T.e. cells.js организует «расплывание» данных (и, в некотором роде, реакции) по ячейкам.
Видимые пользователью блоки пользователького интерфейса
подписываются на значениe ячейки, которую отображают, и обновляются автоматически.
Cells.js позволяет связывать hash-фрагменты URL с ячейками. Так что кнопка «назад», которая просто меняет URL страницы, работает автоматически. При этом просто меняется значение одной из исходных ячеек, и остальная часть пользователького интерфейса обновляется соответственно.
Cells.js реализует получение данных с сервера HTTP GET-запросом как один из видов вычисления значения. Пока запрос загружается, значение ячейки – undefined. Зависимые от этой ячейки блоки интерфейса обычно реагируют на это отображением индикатора загрузки. Это поведение реализуется автоматически в коде связывания ячейки и HTML-шаблона с блоком.
URL GET-запросов как правило берется из других ячеек. В результате REST API Membase большей частью реально реализован через гипертекст (что соответствует каноническому определению REST). Ссылки на GET-запросы берутся из ответов на другие запросы.
Изменение ячейки с URL побуждает зависимую ячейку с содержимым этого URL выполнить запрос повторно с новым URL.
Так, например, реакция пользователя может поменять имя (или какой-либо идентификатор) выделенного обьекта. Зависимая от
имени ячейка может находить URL этого обьекта по имени в «листинге» обьектов, который содержит список пар имя – URL, и ячейка с описанием этого обьекта может просто получать его GET-запросом с сервера. Смена выделенного имени будет автоматически приводить к получению актуального описания выделенного обьекта, и отображение этой ячейки будет также меняться автоматически.
Считаю, что такой способ организации пользователького интерфейса является очень логичным и продуктивным. Он позволил в очень короткие сроки реализовать довольно продвинутый web-интерфейс Membase.
На момент написания этого текста cells все еще является частью Membase. Как и весь код Membase, код cells доступен по лицензии Apache версии 2.0. В ближайшее время планирую закончить работу по вынесению cells в отдельный независимый free software проект.