- PVSM.RU - https://www.pvsm.ru -

На сегодняшний момент существует масса JavaScript-библиотек для создания rich-client приложений. Кроме всем известных Knockout, Angular.JS и Ember есть великое множество других фреймворков, и каждый имеет свою особенность — кто-то пропагандирует минимализм, а кто-то — идеологическую чистоту и соответствие философии MVC. При всём этом многообразии, регулярно появляются всё новые и новые библиотеки. Из последнего, что упоминалось на хабре — Warp9 [1] и Matreshka.js [2]. В связи с этим хочется рассказать о собственной поделке, встречайте, JohnSmith — простой и легковесный JavaScript фреймворк для построения UI.
Прежде всего, хочется сказать, что JohnSmith писался не ради какого-то академического интереса и не для устранения того самого фатального недостатка. Совсем наоборот, JohnSmith зародился в реальном проекте, затем мигрировал из проекта в проект, постепенно улучшаясь и меняя свою форму. И вот теперь он материализовался как полноценная open-source библиотека.
Для демонстрации возможностей JohnSmith, напишем простейшее приложение со следующей функциональностью:
Имеется поле ввода, в которое пользователь пишет своё имя. Как только имя введено, показываем сообщение: Hello, %username%.
Кому сразу хочется увидеть результат: вот готовый User Greeter [3].
Начнём с создания 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 мы вручную указываем зависимости, т.к. за сценой нет никакой магии авто-трекинга.
Модель Вида готова, теперь опишем Вид.
Начнём с создания класса:
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");
Итак, на этом наше мини-приложение закончено, результат можно увидеть тут [3]. Этот пример демонстрирует основную философию фреймворка, но чтобы больше узнать о возможностях JohnSmith, проясним некоторые детали.
Основа связывания в 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);
// ...
Вид в JohnSmith — это атомарная единица для построения интерфейса. Каждый Вид является полностью независимым и обеспечивает возможность повторного использования. Интерфейс всего приложения составляется из отдельных Видов, путём построения «дерева» (composite pattern [4]). То есть, имеется один главный Вид, у него есть дочерние Виды, у каждого из дочерних есть свои дочерние Виды и т.д. Композиция достигается несколькими способами:
var ParentView = function(){
this.init = function(){
this.addChild(".destination", new ChildView(), new ChildViewModel()); // <--
}
};
var ParentView = function(){
this.init = function(viewModel){
this.bind(viewModel.details).to(".details", DetailsView); // <--
}
};
В качестве заключения, обозначим особенности JohnSmith:
model.set('firstName', 'John')). Такой подход обеспечивает тесную дружбу с IDE и отлично сочетается с инструментами типа TypeScript [5] или ScriptSharp [6];На этом всё, спасибо за внимание, ждём конструктивной критики!
Автор: admax
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/49574
Ссылки в тексте:
[1] Warp9: http://habrahabr.ru/post/198158/
[2] Matreshka.js: http://habrahabr.ru/post/196146/
[3] User Greeter: http://john-smith-js.com/index.html
[4] composite pattern: http://en.wikipedia.org/wiki/Composite_pattern
[5] TypeScript: http://www.typescriptlang.org/
[6] ScriptSharp: http://scriptsharp.com/
[7] Источник: http://habrahabr.ru/post/203890/
Нажмите здесь для печати.