JohnSmith — простой и легковесный JavaScript-фреймворк для построения UI

от автора

На сегодняшний момент существует масса 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *