На сегодняшний момент существует масса JavaScript-библиотек для создания rich-client приложений. Кроме всем известных Knockout, Angular.JS и Ember есть великое множество других фреймворков, и каждый имеет свою особенность — кто-то пропагандирует минимализм, а кто-то — идеологическую чистоту и соответствие философии MVC. При всём этом многообразии, регулярно появляются всё новые и новые библиотеки. Из последнего, что упоминалось на хабре — Warp9 и Matreshka.js. В связи с этим хочется рассказать о собственной поделке, встречайте, JohnSmith — простой и легковесный JavaScript фреймворк для построения UI.
Прежде всего, хочется сказать, что JohnSmith писался не ради какого-то академического интереса и не для устранения того самого фатального недостатка. Совсем наоборот, JohnSmith зародился в реальном проекте, затем мигрировал из проекта в проект, постепенно улучшаясь и меняя свою форму. И вот теперь он материализовался как полноценная open-source библиотека.
Пример
Для демонстрации возможностей JohnSmith, напишем простейшее приложение со следующей функциональностью:
Имеется поле ввода, в которое пользователь пишет своё имя. Как только имя введено, показываем сообщение: Hello, %username%.
Кому сразу хочется увидеть результат: вот готовый User Greeter.
View Model
Начнём с создания View Model, и прежде всего, напишем «класс»:
var GreeterViewModel = function(){ }
View Model обычно «выставляет» во внешний мир объекты, изменения которых могут отслеживаться из вне. В JohnSmith эти объекты называются bindable. Добавим поле для хранения имени пользователя:
var GreeterViewModel = function(){ this.userName = js.bindableValue(); };
Это поле (userName) будет использоваться для двунаправленного связывания в Виде. Добавим еще одно поле, которое будет формировать текст сообщения. Это поле зависит от userName, поэтому опишем его в виде dependentValue:
var GreeterViewModel = function(){ this.userName = js.bindableValue(); this.greetMessage = js.dependentValue( this.userName, function(userNameValue){ if (userNameValue) { return "Hello, " + userNameValue + "!"; } return "Please, enter your name"; }); };
js.dependentValue похож на computed в knockout, за исключением того, что в JohnSmith мы вручную указываем зависимости, т.к. за сценой нет никакой магии авто-трекинга.
Модель Вида готова, теперь опишем Вид.
View
Начнём с создания класса:
var GreeterView = function(){ }
Вид — это совокупность разметки и логики связи этой разметки с внешним миром. Разметка описывается в поле template, а логика — в методе init:
var GreeterView = function(){ this.template = "...здесь описываем разметку..."; this.init = function(){ // здесь описываем логику } };
В нашем тестовом примере разметка довольно-таки простая, поэтому запишем её прямо в поле template:
var GreeterView = function(){ this.template = "<p>Enter your name: <input type='text'/></p>" + "<p class='message'></p>"; this.init = function(){ // здесь скоро будет логика }; };
Теперь переходим к методу init. Во-первых, JohnSmith подразумевает, что каждый Вид работает с определённой Моделью Вида, поэтому добавим параметр viewModel:
var GreeterView = function(){ this.template = "<p>Enter your name: <input type='text'/></p>" + "<p class='message'></p>"; this.init = function(viewModel){ // <--- // здесь скоро будет логика }; };
Дальше наша задача состоит в том, чтобы связать свойства Модели Вида с разметкой, которую «отрисует» наш Вид. JohnSmith предоставляет синтаксис для настройки этой связи непосредственно в js-коде. Для нашего случая это будет выглядеть так:
var GreeterView = function(){ this.template = "<p>Enter your name: <input type='text'/></p>" + "<p class='message'></p>"; this.init = function(viewModel){ this.bind(viewModel.userName).to("input"); // <--- this.bind(viewModel.greetMessage).to(".message"); // <--- }; };
Теперь всё готово и нам нужно только отрисовать наш вид (подразумевается, что на странице есть элемент с id=’greeter’):
js.renderView(GreeterView, new GreeterViewModel()).to("#greeter");
Итак, на этом наше мини-приложение закончено, результат можно увидеть тут. Этот пример демонстрирует основную философию фреймворка, но чтобы больше узнать о возможностях JohnSmith, проясним некоторые детали.
Binding
Основа связывания в JohnSmith — это observable-объекты (как в knockout). Создаются эти объекты одним из методов:
js.bindableValue— обычный observable объект;js.dependentValue— значение, зависящее от других объектов;js.bindableList— observable-коллекция, уведомляет подписчиков о добавлении/удалении элементов.
Непосредственно связывание объекта A и слушателя B настраивается кодом вида:
js.bind(A).to(B);
Например так:
var firstName = js.bindableValue(); // создаём объект js.bind(firstName).to(".firstName"); // привязываем к jQuery-селектору firstName.setValue("John"); // изменяем значение объекта
Внутри Вида код привязки немного меняется:
// мы внутри метода init некоторого Вида this.bind(viewModel.firstName).to(".firstName");
И в этом случае поиск по селектору .firstName сработает только внутри разметки данного Вида, а не во всём документе. Благодаря этому обеспечивается полная независимость вида от внешнего окружения.
Синтаксис js.bind(A).to(B) позволяет сочетать «декларативный» стиль с императивным и использовать jQuery-style в тех случаях, где это необходимо:
// это больше похоже на декларативный стиль: js.bind(firstName).to(".firstName"); js.bind(firstName).to( function(newValue, oldValue){ // <-- в качестве обработчика используется функция // здесь мы можем использовать jQuery как обычно, // например, скрыть/показать какую-то панель в зависимости // от значений newValue/oldValue, добавить класс, запустить анимацию и т.п. });
Если в качестве bindable-объекта передать обычное (не observable) значение, то произойдёт единовременная синхронизация с интерфейсом. Это позволяет единообразно обрабатывать как observable так и «обычные» поля View Model:
var ViewModel = function(){ this.firstName = "John"; // static value this.lastName = js.bindableValue(); // observable value }; //... // somewhere in the View: this.bind(viewModel.firstName).to(".firstName"); // will sync only once this.bind(viewModel.lastName).to(".lastName"); // will sync on every change
Для отрисовки сложных объектов может использоваться дочерний Вид:
var ViewModel = function(){ this.myAddress = js.bindableValue(); this.initState = function(){ this.myAddress.setValue({ country: 'Russia', city: js.bindableValue(); }); }; }; // ... this.bind(viewModel.myAddress).to(".address", AddressView); // ...
Views Composition
Вид в JohnSmith — это атомарная единица для построения интерфейса. Каждый Вид является полностью независимым и обеспечивает возможность повторного использования. Интерфейс всего приложения составляется из отдельных Видов, путём построения «дерева» (composite pattern). То есть, имеется один главный Вид, у него есть дочерние Виды, у каждого из дочерних есть свои дочерние Виды и т.д. Композиция достигается несколькими способами:
- непосредственное добавление дочернего вида:
var ParentView = function(){ this.init = function(){ this.addChild(".destination", new ChildView(), new ChildViewModel()); // <-- } }; - использование Вида для отрисовки bindable-значения:
var ParentView = function(){ this.init = function(viewModel){ this.bind(viewModel.details).to(".details", DetailsView); // <-- } };
Заключение
В качестве заключения, обозначим особенности JohnSmith:
- компануемость UI позволяет с легкостью использовать JohnSmith для проектов любого размера. При этом с возрастанием сложности легко удаётся держать код под контролем. Это достигается модульностью и четким разделением ответственности между Видом и Моделью;
- JohnSmith очень простой — всего две основные концепции (View и Bindable), да и те хорошо известны любому программисту, работавшему с UI. Никакого сдвига парадигмы и никакой магии за сценой;
- JohnSmith оперирует обычными объектами с обычными полями и методами. Это значит, что Вам не придётся осуществлять какие-либо действия полагаясь на строчные идентификаторы (типа
model.set('firstName', 'John')). Такой подход обеспечивает тесную дружбу с IDE и отлично сочетается с инструментами типа TypeScript или ScriptSharp; - JohnSmith манипулирует элементами DOM из JavaScript-кода, поэтому он нуждается в jQuery.
На этом всё, спасибо за внимание, ждём конструктивной критики!
ссылка на оригинал статьи http://habrahabr.ru/post/203890/
Добавить комментарий